content: package zemberek.core.math;
  
  import static java.lang.Math.abs;
  import static java.lang.Math.sqrt;
  
  import com.google.common.base.Splitter;
  import java.io.DataInputStream;
  import java.io.DataOutputStream;
  import java.io.File;
  import java.io.IOException;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Locale;
  import java.util.regex.Pattern;
  import zemberek.core.io.SimpleTextReader;
  import zemberek.core.text.Regexps;
  
  public class FloatArrays {
  
    public static final float[] ZERO_LENGTH_ARRAY = new float[0];
    public static final float[][] ZERO_LENGTH_MATRIX = new float[0][0];
    public static final Pattern FEATURE_LINES_PATTERN = Pattern
        .compile("(?:\\[)(.+?)(?:\\])", Pattern.DOTALL | Pattern.MULTILINE);
  
    // do not allow instantiation
    private FloatArrays() {
    }
  
    /**
     * @return true if difference is smaller or equal to range
     */
    public static boolean inRange(float d1, float d2, float range) {
      return Math.abs(d1 - d2) <= range;
    }
  
    /**
     * @param input float array
     * @return reverse of the float array
     */
    public static float[] reverse(float[] input) {
      float[] result = new float[input.length];
      for (int i = 0; i < input.length; i++) {
        result[input.length - i - 1] = input[i];
      }
      return result;
    }
  
    /**
     * @param input int array
     * @return float array converted from input int array
     */
    public static float[] convert(int[] input) {
      float[] data = new float[input.length];
      int k = 0;
      for (int i : input) {
        data[k++] = i;
      }
      return data;
    }
  
    /**
     * @param input 2d int array
     * @return 2d float array converted from 2d int array
     */
    public static float[][] convert(int[][] input) {
      float[][] data = new float[input.length][];
      int k = 0;
      for (int[] i : input) {
        data[k] = new float[i.length];
        int j = 0;
        for (int ii : i) {
          data[k][j++] = ii;
        }
        k++;
      }
      return data;
    }
  
    /**
     * @param input float array
     * @return float array converted from input float array
     */
    public static float[] convert(double[] input) {
      float[] data = new float[input.length];
      int k = 0;
      for (double i : input) {
        data[k++] = (float) i;
      }
      return data;
    }
  
    /**
     * @param d1 input float array
     * @param d2 input float array
     * @param range float input
     * @return true if the difference between elements of d1 and d2 is smaller than or equal to given
     * range
     */
    public static boolean arrayEqualsInRange(float[] d1, float[] d2, float range) {
      validateArrays(d1, d2);
      for (int i = 0; i < d1.length; i++) {
        if (Math.abs(d1[i] - d2[i]) > range) {
          return false;
        }
      }
      return true;
    }
  
    /**
     * @return true if two input float arrays are equal
     */
    public static boolean arrayEquals(float[] d1, float[] d2) {
      validateArrays(d1, d2);
      return Arrays.equals(d1, d2);
    }
  
    /**
     * @return the float array after appending zeros to its end with the given amount
     * @throws IllegalArgumentException when amount input is negative
     */
    public static float[] appendZeros(float[] darray, int zeroAmountToAppend) {
      if (zeroAmountToAppend < 0) {
        throw new IllegalArgumentException(
            "Cannot append negative amount of zeros. Amount:" + zeroAmountToAppend);
      }
      return Arrays.copyOf(darray, darray.length + zeroAmountToAppend);
    }
  
    public static float[] normalize16bitLittleEndian(byte[] bytez) {
      return normalize16bitLittleEndian(bytez, bytez.length);
    }
  
    /**
     * @param bytez input byte array
     * @param amount input, size of the byte array
     * @return float array including the normalized float value of each byte elements as Little-Endian
     * representation For 0xABCD: Big-Endian Rep.-->0xABCD Little-Endian Rep-->0xCDBA
     */
    public static float[] normalize16bitLittleEndian(byte[] bytez, int amount) {
      if ((amount & 1) != 0) {
        throw new IllegalArgumentException(
            "Amount of bytes must be an order of 2. But it is: " + amount);
      }
      float[] result = new float[amount / 2];
      for (int i = 0; i < amount; i += 2) {
        final int val = (short) (bytez[i + 1] << 8) | (bytez[i] & 0xff);
        if (val >= 0) {
          result[i >>> 1] = (float) val / Short.MAX_VALUE;
        } else {
          result[i >>> 1] = -(float) val / Short.MIN_VALUE;
        }
      }
      return result;
    }
  
    /**
     * @param input input float array
     * @return byte array including the de-normalized 16-bit Big-Endian representations of float
     * values in float array
     */
    public static byte[] denormalize16BitLittleEndian(float[] input) {
      byte[] result = new byte[input.length * 2];
      for (int i = 0; i < input.length; i++) {
        int denorm;
        if (input[i] < 0) {
          denorm = (int) (-input[i] * Short.MIN_VALUE);
        } else {
          denorm = (int) (input[i] * Short.MAX_VALUE);
        }
        result[i * 2] = (byte) (denorm & 0xff);
        result[i * 2 + 1] = (byte) (denorm >>> 8);
      }
      return result;
    }
  
    /**
     * @param input input float array
     * @param bitsPerSample input as bit number
     * @return byte array including the de-normalized n-bit Big-Endian representations of float values
     * in float array where n is bitsPerSample
     */
    static byte[] denormalizeLittleEndian(float[] input, int bitsPerSample) {
      int bytesPerSample = bitsPerSample % 8 == 0 ? bitsPerSample / 8 : bitsPerSample / 8 + 1;
      int maxVal = 1 << bitsPerSample - 1;
      byte[] result = new byte[input.length * bytesPerSample];
      for (int i = 0; i < input.length; i++) {
        int denorm;
        if (input[i] < 0) {
          denorm = (int) (-input[i] * maxVal);
        } else {
          denorm = (int) (input[i] * maxVal);
        }
        for (int j = 0; j < bytesPerSample; j++) {
          result[i * bytesPerSample + j] = (byte) ((denorm >>> j * 8) & 0xff);
        }
      }
      return result;
    }
  
    /**
     * gets a float array with values between -1.0 and 1.0 and converts it to an integer in the range
     * of [0,max]
     *
     * @param input float array
     * @param max max integer value.
     * @return an integer array/
     */
    public static int[] toUnsignedInteger(float[] input, int max) {
      if (max < 1) {
        throw new IllegalArgumentException("Maximum int value must be positive. But it is:" + max);
      }
      int[] arr = new int[input.length];
      float divider = (float) ((double) max / 2.0);
      for (int i = 0; i < input.length; i++) {
        float d = input[i];
        if (d < -1.0 || d > 1.0) {
          throw new IllegalArgumentException(
              "Array value should be between -1.0 and 1.0. But it is: " + d);
        }
        arr[i] = (int) (input[i] * divider);
      }
      return arr;
    }
  
    /**
     * Converts to an integer array. if values are outside of the given boundaries, throws Exception.
     */
    public static int[] toInteger(float[] input, int min, int max) {
      validateArray(input);
      int[] result = new int[input.length];
      int i = 0;
      for (float v : input) {
        if (!Float.isFinite(v) || Float.isNaN(v)) {
          throw new IllegalStateException("Value" + v + " cannot be converted.");
        }
        if (v < min || v > max) {
          throw new IllegalStateException("Value" + v + "is outside of min-max boundaries.");
        }
        result[i] = (int) v;
        i++;
      }
      return result;
    }
  
    /**
     * finds the maximum value of an array.
     *
     * @param input input array
     * @return maximum value.
     * @throws IllegalArgumentException if array is empty or null.
     */
    public static float max(float... input) {
      validateArray(input);
      float max = input[0];
      for (int i = 1; i < input.length; i++) {
        if (input[i] > max) {
          max = input[i];
        }
      }
      return max;
    }
  
    /**
     * Trims the values of an array against a minimum and maximum value.
     *
     * @param input input array
     * @throws IllegalArgumentException if array is empty or null.
     */
    public static void trimValues(float[] input, float minVal, float maxVal) {
      validateArray(input);
      for (int i = 0; i < input.length; i++) {
        if (input[i] < minVal) {
          input[i] = minVal;
        } else if (input[i] > maxVal) {
          input[i] = maxVal;
        }
      }
    }
  
    /**
     * Finds the maximum absolute value of an array.
     *
     * @param input input array
     * @return maximum absolute value.
     * @throws IllegalArgumentException if array is empty or null.
     */
    public static float absMax(float... input) {
      validateArray(input);
      float max = Math.abs(input[0]);
      for (int i = 1; i < input.length; i++) {
        float abs = Math.abs(input[i]);
        if (abs > max) {
          max = abs;
        }
      }
      return max;
    }
  
    /**
     * Formats a float array as string using English Locale.
     */
    public static String format(float... input) {
      return format(10, 3, " ", input);
    }
  
    /**
     * Formats a float array as string using English Locale.
     */
    public static String format(int fractionDigits, float... input) {
      return format(fractionDigits, " ", input);
    }
  
    /**
     * Formats a float array as string using English Locale.
     */
    public static String format(int fractionDigits, String delimiter, float... input) {
      StringBuilder sb = new StringBuilder();
      String formatStr = "%." + fractionDigits + "f";
      int i = 0;
      for (float v : input) {
        sb.append(String.format(Locale.ENGLISH, formatStr, v));
        if (i++ < input.length - 1) {
          sb.append(delimiter);
        }
      }
      return sb.toString();
    }
  
    /**
     * Formats a float array as string using English Locale.
     */
    public static String format(int rightPad, int fractionDigits, String delimiter, float... input) {
      StringBuilder sb = new StringBuilder();
      String formatStr = "%." + fractionDigits + "f";
      int i = 0;
      for (float v : input) {
        String num = String.format(Locale.ENGLISH, formatStr, v);
        sb.append(String.format(Locale.ENGLISH, "%-" + rightPad + "s", num));
        if (i++ < input.length - 1) {
          sb.append(delimiter);
        }
      }
      return sb.toString().trim();
    }
  
    /**
     * finds the minimum value of an array.
     *
     * @param input input array
     * @return minimum value.
     * @throws IllegalArgumentException if array is empty or null.
     */
    public static float min(float... input) {
      validateArray(input);
      float min = input[0];
      for (int i = 1; i < input.length; i++) {
        if (input[i] < min) {
          min = input[i];
        }
      }
      return min;
    }
  
    /**
     * Finds the minimum absolute value of an array.
     *
     * @param input input array
     * @return minimum value.
     * @throws IllegalArgumentException if array is empty or null.
     */
    public static float absMin(float... input) {
      validateArray(input);
      float min = Math.abs(input[0]);
      for (int i = 1; i < input.length; i++) {
        float abs = Math.abs(input[i]);
        if (abs < min) {
          min = abs;
        }
      }
      return min;
    }
  
    /**
     * checks whether the input array is null or empty
     *
     * @param input input float array
     */
    public static void validateArray(float... input) {
      if (input == null) {
        throw new IllegalArgumentException("array is null!");
      } else if (input.length == 0) {
        throw new IllegalArgumentException("array is empty!");
      }
    }
  
    /**
     * @param input input array
     * @return index at which the maximum value of input is, minimum index is returned when multiple
     * maximums
     */
    public static int maxIndex(float... input) {
      validateArray(input);
      float max = input[0];
      int index = 0;
      for (int i = 1; i < input.length; i++) {
        if (input[i] > max) {
          max = input[i];
          index = i;
        }
      }
      return index;
    }
  
    /**
     * @param input input array
     * @return index at which the minimum value element of input is, minimum index is returned when
     * multiple minimums
     */
    public static int minIndex(float... input) {
      validateArray(input);
      float min = input[0];
      int minIndex = 0;
      for (int i = 1; i < input.length; i++) {
        if (input[i] < min) {
          min = input[i];
          minIndex = i;
        }
      }
      return minIndex;
    }
  
    /**
     * @param input input
     * @return sum of all elements in array
     */
    public static float sum(float... input) {
      float sum = 0;
      for (float v : input) {
        sum += v;
      }
      return sum;
    }
  
    /**
     * @param a1 input
     * @param a2 input
     * @return float array of which elements are the sum of 2 input arrays' elements
     */
    public static float[] sum(float[] a1, float[] a2) {
      validateArrays(a1, a2);
      float[] sum = new float[a1.length];
      for (int i = 0; i < a1.length; i++) {
        sum[i] = a1[i] + a2[i];
      }
      return sum;
    }
  
    /**
     * sums two float vector. result is written to first vector.
     *
     * @param first first vector.
     * @param second second vector
     */
    public static void addToFirst(float[] first, float[] second) {
      validateArrays(first, second);
      for (int i = 0; i < first.length; i++) {
        first[i] = first[i] + second[i];
      }
    }
  
    /**
     * Adds a value to all elements of the [data] array.
     */
    public static void addToAll(float[] data, float valueToAdd) {
      validateArray(data);
      for (int i = 0; i < data.length; i++) {
        data[i] += valueToAdd;
      }
    }
  
    /**
     * sums two float vectors (second vector is scaled by scale factor). result is written to first
     * vector.
     *
     * @param first first vector.
     * @param second second vector
     * @param scale scale factor for second
     */
    public static void addToFirstScaled(float[] first, float[] second, float scale) {
      validateArrays(first, second);
      for (int i = 0; i < first.length; i++) {
        first[i] = first[i] + second[i] * scale;
      }
    }
  
    /**
     * @param input input float array
     * @return an array containing square-values of the input array's elements
     */
    public static float[] square(float... input) {
      float[] res = new float[input.length];
      for (int i = 0; i < input.length; i++) {
        res[i] = input[i] * input[i];
      }
      return res;
    }
  
    public static void squareInPlace(float... input) {
      for (int i = 0; i < input.length; i++) {
        input[i] = input[i] * input[i];
      }
    }
  
    /**
     * Subtracts two float vector.
     *
     * @param a1 first vector.
     * @param a2 second vector
     * @return Subtraction result
     */
    public static float[] subtract(float[] a1, float[] a2) {
      validateArrays(a1, a2);
      float[] diff = new float[a1.length];
      for (int i = 0; i < a1.length; i++) {
        diff[i] = a1[i] - a2[i];
      }
      return diff;
    }
  
    /**
     * substracts two float vector. result is written to first vector.
     *
     * @param first first vector.
     * @param second second vector
     */
    public static void subtractFromFirst(float[] first, float[] second) {
      validateArrays(first, second);
      for (int i = 0; i < first.length; i++) {
        first[i] = first[i] - second[i];
      }
    }
  
    /**
     * @param a1 input float array
     * @param a2 input float array
     * @return the array produced after multiplying the elements of input arrays
     */
    public static float[] multiply(float[] a1, float[] a2) {
      validateArrays(a1, a2);
      float[] mul = new float[a1.length];
      for (int i = 0; i < a1.length; i++) {
        mul[i] = a1[i] * a2[i];
      }
      return mul;
    }
  
    /**
     * @param a1 input float array
     * @param a2 input float array
     * @return the dot product value of elements in input arrays
     */
  
    public static float dotProduct(float[] a1, float[] a2) {
      return sum(multiply(a1, a2));
    }
  
    /**
     * multiplies two float vectors and result is written to the first vector.
     *
     * @param first first vector
     * @param second second vector.
     */
    public static void multiplyToFirst(float[] first, float[] second) {
      validateArrays(first, second);
      for (int i = 0; i < first.length; i++) {
        first[i] = first[i] * second[i];
      }
    }
  
    /**
     * Multiplies all elements of a vector with a float number and returns a new vector
     *
     * @param a1 vector
     * @param b scale factor
     * @return new scaled vector
     */
    public static float[] scale(float[] a1, float b) {
      validateArray(a1);
      float[] mul = new float[a1.length];
      for (int i = 0; i < a1.length; i++) {
        mul[i] = a1[i] * b;
      }
      return mul;
    }
  
    /**
     * Multiplies all elements of a vector with a float number
     *
     * @param a1 vector
     * @param b scale factor
     */
    public static void scaleInPlace(float[] a1, float b) {
      validateArray(a1);
      for (int i = 0; i < a1.length; i++) {
        a1[i] = a1[i] * b;
      }
    }
  
    /**
     * Calculates mean of a vector.
     *
     * @param input float array
     * @return mean
     */
    public static float mean(float... input) {
      validateArray(input);
      return sum(input) / input.length;
    }
  
    /**
     * for A=[a0, a1, ...,an] for B=[b0, b1, ...,bn] returns C=|a0-b0|+|a1-b1|+...+|an-bn|
     *
     * @param a input array a
     * @param b input array b
     * @return squared sum of array elements.
     */
    public static float absoluteSumOfDifferences(float[] a, float[] b) {
      return sum(absoluteDifference(a, b));
    }
  
    /**
     * for A=[a0, a1, ...,an] for B=[b0, b1, ...,bn] returns C=[|a0-b0|,|a1-b1|,...,|an-bn|]
     *
     * @param a input array a
     * @param b input array b
     * @return squared sum of array elements.
     */
    public static float[] absoluteDifference(float[] a, float[] b) {
      validateArrays(a, b);
      float[] diff = new float[a.length];
      for (int i = 0; i < a.length; i++) {
        diff[i] += abs(a[i] - b[i]);
      }
      return diff;
    }
  
    /**
     * checks whether one of the input arrays are null or not, and whether their length is equal or
     * not
     *
     * @param a1 input float array
     * @param a2 input float array
     */
    public static void validateArrays(float[] a1, float[] a2) {
      if (a1 == null) {
        throw new NullPointerException("first array is null!");
      }
      if (a2 == null) {
        throw new NullPointerException("second array is null!");
      }
      if (a1.length != a2.length) {
        throw new IllegalArgumentException("Array sizes must be equal. But, first:"
            + a1.length + ", and second:" + a2.length);
      }
    }
  
    /**
     * for A=[a0, a1, ...,an] returns a0*a0+a1*a1+....+an*an
     *
     * @param array input array
     * @return squared sum of array elements.
     */
    public static float squaredSum(float[] array) {
      float result = 0;
      validateArray(array);
      for (float a : array) {
        result += a * a;
      }
      return result;
    }
  
    public static float squaredSumOfDifferences(float[] a, float[] b) {
      return (squaredSum(subtract(a, b)));
    }
  
    /**
     * @param input input float array
     * @return variance value of the elements in the input array
     */
    public static float variance(float[] input) {
      float sigmaSquare = 0;
      float mean = mean(input);
      for (float a : input) {
        float meanDiff = a - mean;
        sigmaSquare += meanDiff * meanDiff;
      }
      return sigmaSquare / (input.length - 1);
    }
  
    /**
     * @param a input float array
     * @return standard deviation value of the elements in the input array
     */
    public static float standardDeviation(float[] a) {
      return (float) sqrt(variance(a));
    }
  
    /**
     * @param a input float array
     * @return true if array includes at least one Not-a-Number (NaN) value, false otherwise
     */
    public static boolean containsNaN(float[] a) {
      for (float v : a) {
        if (Float.isNaN(v)) {
          return true;
        }
      }
      return false;
    }
  
    /**
     * @param a input float array
     * @return true if array includes at least one Not-a-Number (NaN) or infinite value, false
     * otherwise
     */
    public static boolean containsNanOrInfinite(float[] a) {
      for (float v : a) {
        if (Float.isNaN(v) || !Float.isFinite(v)) {
          return true;
        }
      }
      return false;
    }
  
    /**
     * replaces the elements smaller than minValue with the minValue
     *
     * @param var input float array
     * @param minValue float
     */
    public static void floorInPlace(float[] var, float minValue) {
      for (int k = 0; k < var.length; k++) {
        if (var[k] < minValue) {
          var[k] = minValue;
        }
      }
    }
  
    /**
     * If a data point is non-zero and below 'floor' make it equal to floor
     *
     * @param data the data to floor
     * @param floor the floored value
     */
    public static void nonZeroFloorInPlace(float[] data, float floor) {
      for (int i = 0; i < data.length; i++) {
        if (data[i] != 0.0f && data[i] < floor) {
          data[i] = floor;
        }
      }
    }
  
    /**
     * Normalize the given data.
     *
     * @param data the data to normalize
     */
    public static void normalizeInPlace(float[] data) {
      float sum = sum(data);
      scaleInPlace(data, 1f / sum);
    }
  
    public static float[] fromString(String str, String delimiter) {
      List<String> tokens = Splitter.on(delimiter).trimResults().omitEmptyStrings().splitToList(str);
      float[] result = new float[tokens.size()];
      for (int i = 0; i < result.length; i++) {
        result[i] = Float.parseFloat(tokens.get(i));
      }
      return result;
    }
  
    public static void serialize(DataOutputStream dos, float[] data) throws IOException {
      dos.writeInt(data.length);
      for (float v : data) {
        dos.writeFloat(v);
      }
    }
  
    public static void serializeRaw(DataOutputStream dos, float[] data) throws IOException {
      for (float v : data) {
        dos.writeFloat(v);
      }
    }
  
    public static void serialize(DataOutputStream dos, float[][] data) throws IOException {
      dos.writeInt(data.length);
      for (float[] floats : data) {
        serialize(dos, floats);
      }
    }
  
    public static void serializeRaw(DataOutputStream dos, float[][] data) throws IOException {
      for (float[] floats : data) {
        serializeRaw(dos, floats);
      }
    }
  
    public static float[] deserialize(DataInputStream dis) throws IOException {
      int amount = dis.readInt();
      float[] result = new float[amount];
      for (int i = 0; i < amount; i++) {
        result[i] = dis.readFloat();
      }
      return result;
    }
  
    public static float[] deserializeRaw(DataInputStream dis, int amount) throws IOException {
      float[] result = new float[amount];
      for (int i = 0; i < amount; i++) {
        result[i] = dis.readFloat();
      }
      return result;
    }
  
    public static void deserializeRaw(DataInputStream dis, float[] result) throws IOException {
      for (int i = 0; i < result.length; i++) {
        result[i] = dis.readFloat();
      }
    }
  
    public static float[][] deserialize2d(DataInputStream dis) throws IOException {
      int amount = dis.readInt();
      float[][] result = new float[amount][];
      for (int i = 0; i < amount; i++) {
        result[i] = deserialize(dis);
      }
      return result;
    }
  
    public static void deserialize2DRaw(DataInputStream dis, float[][] result) throws IOException {
      for (float[] row : result) {
        deserializeRaw(dis, row);
      }
    }
  
    public static float[][] clone2D(float[][] result) {
      float[][] arr = new float[result.length][];
      for (int i = 0; i < arr.length; i++) {
        arr[i] = result[i].clone();
      }
      return arr;
    }
  
    public static float[] to1D(float[][] matrix) {
      if (matrix.length == 0) {
        return ZERO_LENGTH_ARRAY;
      }
      int dimension = matrix[0].length;
      if (dimension == 0) {
        return ZERO_LENGTH_ARRAY;
      }
      float[] result = new float[matrix.length * dimension];
      for (int i = 0; i < matrix.length; i++) {
        float[] floats = matrix[i];
        if (floats.length != dimension) {
          throw new IllegalStateException("Unexpected array size.");
        }
        System.arraycopy(floats, 0, result, i * dimension, dimension);
      }
      return result;
    }
  
    public static float[][] toMatrix(float[] vector, int dimension) {
      if (vector.length == 0) {
        return ZERO_LENGTH_MATRIX;
      }
      if (dimension <= 0) {
        throw new IllegalArgumentException("Dimension must be a positive number.");
      }
      int vectorCount = vector.length / dimension;
      if (vectorCount * dimension != vector.length) {
        throw new IllegalStateException("vector length is not a factor of dimension.");
      }
      float[][] result = new float[vectorCount][dimension];
      for (int i = 0; i < vectorCount; i++) {
        System.arraycopy(vector, i * dimension, result[i], 0, dimension);
      }
      return result;
    }
  
    /**
     * loads float array from file with format: [1 2 3] [4 5 6]
     */
    public static float[][] loadFromText(File input) throws IOException {
      String wholeThing = new SimpleTextReader(input, "UTF-8").asString();
      List<String> featureBlocks = Regexps.firstGroupMatches(FEATURE_LINES_PATTERN, wholeThing);
      float[][] result = new float[featureBlocks.size()][];
      int i = 0;
      for (String featureBlock : featureBlocks) {
        result[i] = FloatArrays.fromString(featureBlock, " ");
        i++;
      }
      return result;
    }
  
  }
  