/* * Copyright (c) 2017 Jacob Rachiele * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without restriction * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to * do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * USE OR OTHER DEALINGS IN THE SOFTWARE. * * Contributors: * * Jacob Rachiele */ package com.github.signaflo.math.operations; import com.google.common.primitives.Doubles; import com.github.signaflo.math.stats.Statistics; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; /** * Static methods for creating, manipulating, and operating on arrays of primitive doubles. * * @author Jacob Rachiele */ public final class DoubleFunctions { private static final double EPSILON = Math.ulp(1.0); private DoubleFunctions() { } /** * Create and return a new array from the given data. * * @param data the data to create a new array from. * @return a new array from the given data. */ public static double[] arrayFrom(double... data) { return data.clone(); } /** * Create a new array by combining the elements of the input arrays in the order given. * * @param arrays the arrays to combine. * @return a new array formed by combining the elements of the input arrays. */ public static double[] combine(double[]... arrays) { int newArrayLength = 0; for (double[] array : arrays) { newArrayLength += array.length; } double[] newArray = new double[newArrayLength]; newArrayLength = 0; for (double[] array : arrays) { System.arraycopy(array, 0, newArray, newArrayLength, array.length); newArrayLength += array.length; } return newArray; } /** * Create a new array by combining the elements of the input arrays in the order given. * * @param twoDArrays the arrays to combine. * @return a new array formed by combining the elements of the input arrays. */ public static double[][] combine(double[][]... twoDArrays) { int newArrayLength = 0; for (double[][] twoDArray : twoDArrays) { newArrayLength += twoDArray.length; } double[][] newArray = new double[newArrayLength][]; int i = 0; for (double[][] twoDArray : twoDArrays) { for (double[] array : twoDArray) { newArray[i] = array.clone(); i++; } } return newArray; } /** * Append the given value to the end of the original array, and return the result in a new array. * * @param original the array to be appended. * @param value the value to append to the end of the array. * @return a new array consisting of the original array with the given value appended to the end. */ public static double[] append(double[] original, double value) { double[] newArray = new double[original.length + 1]; System.arraycopy(original, 0, newArray, 0, original.length); newArray[original.length] = value; return newArray; } public static double[] push(double value, double[] original) { double[] newArray = new double[original.length + 1]; newArray[0] = value; System.arraycopy(original, 0, newArray, 1, original.length); return newArray; } public static double[][] push(double[] values, double[][] original) { double[][] newArrays = new double[original.length + 1][]; newArrays[0] = values.clone(); for (int i = 0; i < original.length; i++) { newArrays[i + 1] = original[i].clone(); } return newArrays; } /** * Create a new primitive double array from the given collection of numbers. * * @param data the data to use in the new array. * @return a new array from the given collection of numbers. */ public static double[] arrayFrom(Collection<? extends Number> data) { final int size = data.size(); final double[] doubles = new double[size]; Iterator<? extends Number> iterator = data.iterator(); int i = 0; while (iterator.hasNext()) { doubles[i] = iterator.next().doubleValue(); i++; } return doubles; } /** * Create a new primitive double array from the given boxed Number array. * * @param data the data to use in the new array. * @return a new array from the given boxed Number array. */ public static double[] arrayFrom(Number[] data) { final int size = data.length; final double[] doubles = new double[size]; for (int i = 0; i < size; i++) { doubles[i] = data[i].doubleValue(); } return doubles; } /** * Create a new array from the data in the given array at the specified indices. * * @param data the data to create the new array from. * @param indices the indices at which to select data from the given array. * @return a new array from the data in the given array at the specified indices. */ static double[] arrayFrom(double[] data, int... indices) { double[] newArray = new double[indices.length]; for (int i = 0; i < newArray.length; i++) { newArray[i] = data[indices[i]]; } return newArray; } /** * Create and return a new array of the given size with every value set to the given value. * * @param size the number of elements of the new array. * @param value the value to fill every element of the array with. * @return a new array of the given size with every value set to the given value. */ public static double[] fill(final int size, final double value) { final double[] filled = new double[size]; for (int i = 0; i < filled.length; i++) { filled[i] = value; } return filled; } /** * Create a new list of boxed Doubles from the given array of primitive doubles. * * @param data the data to populate the new list with. * @return a new list of boxed Doubles from the given array of primitive doubles. */ public static List<Double> listFrom(final double... data) { return Doubles.asList(data.clone()); } public static List<List<Double>> twoDListFrom(final double[]... data) { List<List<Double>> newList = new ArrayList<>(data.length); for (double[] array : data) { newList.add(Doubles.asList(array.clone())); } return newList; } public static double[][] twoDArrayFrom(final List<List<Double>> data) { double[][] newArray = new double[data.size()][]; for (int i = 0; i < data.size(); i++) { newArray[i] = arrayFrom(data.get(i)); } return newArray; } /** * Return a slice of the data between the given indices. * * @param data the data to slice. * @param from the starting index. * @param to the ending index. The value at this index is excluded from the result. * @return a slice of the data between the given indices. */ public static double[] slice(final double[] data, final int from, final int to) { final double[] sliced = new double[to - from]; System.arraycopy(data, from, sliced, 0, to - from); return sliced; } /** * Transform the given data using a Box-Cox transformation with the given lambda value. * * @param data the data to transform. * @param lambda the Box-Cox parameter. * @return the data transformed using a Box-Cox transformation with the given lambda value. */ public static double[] boxCox(final double[] data, final double lambda) { final double[] boxCoxed = new double[data.length]; if (Math.abs(lambda) < EPSILON) { for (int i = 0; i < boxCoxed.length; i++) { boxCoxed[i] = Math.log(data[i]); } } else { for (int i = 0; i < boxCoxed.length; i++) { boxCoxed[i] = (Math.pow(data[i], lambda) - 1) / lambda; } } return boxCoxed; } /** * Invert the Box-Cox transformation, returning the original untransformed data. * * @param data the transformed data to invert. * @param lambda the Box-Cox parameter used in the transformation. * @return the original, untransformed data in a new array. */ public static double[] inverseBoxCox(final double[] data, final double lambda) { final double[] invBoxCoxed = new double[data.length]; if (Math.abs(lambda) < EPSILON) { for (int i = 0; i < invBoxCoxed.length; i++) { invBoxCoxed[i] = Math.exp(data[i]); } } else { for (int i = 0; i < invBoxCoxed.length; i++) { invBoxCoxed[i] = Math.pow(data[i] * lambda + 1, 1 / lambda); } } return invBoxCoxed; } /** * Take the square root of each element of the given array and return the result in a new array. * * @param data the data to take the square root of. * @return a new array containing the square root of each element. */ public static double[] sqrt(final double... data) { final double[] sqrtData = new double[data.length]; for (int i = 0; i < sqrtData.length; i++) { sqrtData[i] = Math.sqrt(data[i]); } return sqrtData; } // public static double[] reverse(final double... data) { // int n = data.length; // double[] reversed = new double[n]; // for (int i = 0; i < reversed.length; i++, --n) { // reversed[i] = data[n]; // } // return reversed; // } // // public static List<Double> reverseArrayToList(final double... data) { // int n = data.length; // List<Double> reversed = new ArrayList<>(n); // while (n > 0) { // reversed.add(data[--n]); // } // return reversed; // } // public static double[] reverseListToArray(final List<Double> data) { // int n = data.size(); // double[] reversed = new double[data.size()]; // for (int i = 0; i < reversed.length; i++) { // reversed[i] = data.get(--n); // } // return reversed; // } // // public static List<Double> reverse(final List<Double> data) { // int n = data.size(); // List<Double> reversed = new ArrayList<>(n); // while (n > 0) { // reversed.add(data.get(--n)); // } // return reversed; // } /** * Round the given value to the specified precision. * * @param value the value to round. * @param precision the decimal place precision to round to. * @return the given value rounded to the specified precision. */ public static double round(final double value, final int precision) { double scale = Math.pow(10.0, precision); return Math.round(value * scale) / scale; } /** * Round the given values to the specified precision. * * @param values the values to round. * @param precision the decimal place precision to round to. * @return the given values rounded to the specified precision. */ public static double[] round(final double[] values, final int precision) { double[] rounded = new double[values.length]; for (int i = 0; i < values.length; i++) { rounded[i] = round(values[i], precision); } return rounded; } /** * Remove the mean from the given data and return the result in a new array. * * @param data the data to remove the mean from. * @return the data with the mean removed. */ static double[] demean(final double[] data) { final double mean = Statistics.meanOf(data); final double[] demeaned = new double[data.length]; for (int t = 0; t < data.length; t++) { demeaned[t] = data[t] - mean; } return demeaned; } /** * Take the additive inverse, or negative, of each element of the given array and return the result in a new array. * * @param data the data to take the additive inverse of. * @return a new array containing the additive inverse, or negative, of each element. */ static double[] negativeOf(final double[] data) { final double[] negative = new double[data.length]; for (int i = 0; i < negative.length; i++) { negative[i] = -data[i]; } return negative; } public static double[][] copy(double[][] values) { double[][] copied = new double[values.length][]; for (int i = 0; i < values.length; i++) { copied[i] = values[i].clone(); } return copied; } }