All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ucar.ma2.MAMath Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.ma2;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Objects;
import ucar.nc2.util.Misc;

/**
 * Element by element algebra on Arrays
 *
 * @author caron
 * @see Index
 */
public class MAMath {

  /**
   * Add elements of two arrays together, allocating the result array.
   * The result type and the operation type are taken from the type of a.
   *
   * @param a add values from here
   * @param b add values from here
   * @return result = a + b
   * @throws IllegalArgumentException      a and b are not conformable
   * @throws UnsupportedOperationException dont support this data type yet
   */
  public static Array add(Array a, Array b) throws IllegalArgumentException {

    Array result = Array.factory(a.getDataType(), a.getShape());

    if (a.getElementType() == double.class) {
      addDouble(result, a, b);
    } else
      throw new UnsupportedOperationException();

    return result;
  }

  /**
   * Add elements of two arrays together as doubles, place sum in the result array.
   * The values from the arrays a and b are converted to double (if needed),
   * and the sum is converted to the type of result (if needed).
   *
   * @param result result array
   * @param a operand
   * @param b operand
   * @throws IllegalArgumentException a,b,and result are not conformable
   */
  public static void addDouble(Array result, Array a, Array b)
      throws IllegalArgumentException {

    if (!conformable(result, a) || !conformable(a, b))
      throw new IllegalArgumentException();

    IndexIterator iterR = result.getIndexIterator();
    IndexIterator iterA = a.getIndexIterator();
    IndexIterator iterB = b.getIndexIterator();

    while (iterA.hasNext())
      iterR.setDoubleNext(iterA.getDoubleNext() + iterB.getDoubleNext());
  }

  /**
   * Check that two arrays are conformable.
   *
   * @param a operand
   * @param b operand
   * @return true if conformable
   */
  public static boolean conformable(Array a, Array b) {
    return conformable(a.getShape(), b.getShape());
  }

  /**
   * Check that two array shapes are conformable.
   * The shapes must match exactly, except that dimensions of length 1 are ignored.
   *
   * @param shapeA shape of array 1
   * @param shapeB shape of array 2
   * @return true if conformable
   */
  public static boolean conformable(int[] shapeA, int[] shapeB) {
    if (reducedRank(shapeA) != reducedRank(shapeB))
      return false;

    int rankB = shapeB.length;

    int dimB = 0;
    for (int aShapeA : shapeA) {
      //System.out.println(dimA + " "+ dimB);

      //skip length 1 dimensions
      if (aShapeA == 1)
        continue;
      while (dimB < rankB)
        if (shapeB[dimB] == 1) dimB++;
        else break;

      // test same shape (NB dimB cant be > rankB due to first test)
      if (aShapeA != shapeB[dimB])
        return false;
      dimB++;
    }

    return true;
  }

  /**
   * Convert unsigned data to signed data of a wider type.
   *
   * @param unsigned must be of type byte, short, int, or long.
   * @return converted data of type short, int, long, or {@link java.math.BigInteger}.
   */
  public static Array convertUnsigned(Array unsigned) {
    if (unsigned.getElementType().equals(byte.class)) {
      Array result = Array.factory(DataType.SHORT, unsigned.getShape());
      IndexIterator ii = result.getIndexIterator();
      unsigned.resetLocalIterator();
      while (unsigned.hasNext())
        ii.setShortNext( DataType.widenNumber(unsigned.nextByte()).shortValue());
      return result;

    } else if (unsigned.getElementType().equals(short.class)) {
      Array result = Array.factory(DataType.INT, unsigned.getShape());
      IndexIterator ii = result.getIndexIterator();
      unsigned.resetLocalIterator();
      while (unsigned.hasNext())
        ii.setIntNext( DataType.widenNumber(unsigned.nextShort()).intValue());
      return result;

    } else if (unsigned.getElementType().equals(int.class)) {
      Array result = Array.factory(DataType.LONG, unsigned.getShape());
      IndexIterator ii = result.getIndexIterator();
      unsigned.resetLocalIterator();
      while (unsigned.hasNext())
        ii.setLongNext( DataType.widenNumber(unsigned.nextInt()).longValue());
      return result;
    } else if (unsigned.getElementType().equals(long.class)) {
      Array result = ArrayObject.factory(DataType.OBJECT, BigInteger.class, false, Index.factory(unsigned.getShape()));
      IndexIterator ii = result.getIndexIterator();
      unsigned.resetLocalIterator();
      while (unsigned.hasNext())
        ii.setObjectNext( DataType.widenNumber(unsigned.nextLong()));
      return result;
    }

    throw new IllegalArgumentException("Cant convertUnsigned type= "+unsigned.getElementType());
  }

  /**
   * Convert original array to desired type
   *
   * @param org original array
   * @param wantType desired type
   * @return converted data of desired type, or original array if it is already
   */
  public static Array convert( Array org, DataType wantType) {
    if (org == null) return null;
    Class wantClass = wantType.getPrimitiveClassType();
    if (org.getElementType().equals(wantClass))
      return org;

    Array result = Array.factory(wantType, org.getShape());
    copy(wantType, org.getIndexIterator(), result.getIndexIterator());
    return result;
  }

  /**
   * Copy using iterators. Will copy until !from.hasNext().
   *
   * @param dataType use this operation type (eg DataType.DOUBLE uses getDoubleNext())
   * @param from     copy from here
   * @param to       copy to here
   * @throws IllegalArgumentException      a and b are not conformable
   * @throws UnsupportedOperationException dont support this data type
   */
  public static void copy(DataType dataType, IndexIterator from, IndexIterator to) throws IllegalArgumentException {
    if (dataType == DataType.DOUBLE) {
      while (from.hasNext())
        to.setDoubleNext(from.getDoubleNext());
    } else if (dataType == DataType.FLOAT) {
      while (from.hasNext())
        to.setFloatNext(from.getFloatNext());
    } else if (dataType.getPrimitiveClassType() == long.class) {
      while (from.hasNext())
        to.setLongNext(from.getLongNext());
    } else if (dataType.getPrimitiveClassType() == int.class) {
      while (from.hasNext())
        to.setIntNext(from.getIntNext());
    } else if (dataType.getPrimitiveClassType() == short.class) {
      while (from.hasNext())
        to.setShortNext(from.getShortNext());
    } else if (dataType == DataType.CHAR) {
      while (from.hasNext())
        to.setCharNext(from.getCharNext());
    } else if (dataType.getPrimitiveClassType() == byte.class) {
      while (from.hasNext())
        to.setByteNext(from.getByteNext());
    } else if (dataType == DataType.BOOLEAN) {
      while (from.hasNext())
        to.setBooleanNext(from.getBooleanNext());
    } else {
      while (from.hasNext())
        to.setObjectNext(from.getObjectNext());
    }
  }

  /**
   * Copy array a to array result, the result array will be in canonical order
   * The operation type is taken from the type of a.
   *
   * @param result copy to here
   * @param a copy from here
   *
   * @throws IllegalArgumentException      a and b are not conformable
   * @throws UnsupportedOperationException dont support this data type yet
   */
  public static void copy(Array result, Array a) throws IllegalArgumentException {
    Class classType = a.getElementType();
    if (classType == double.class) {
      copyDouble(result, a);
    } else if (classType == float.class) {
      copyFloat(result, a);
    } else if (classType == long.class) {
      copyLong(result, a);
    } else if (classType == int.class) {
      copyInt(result, a);
    } else if (classType == short.class) {
      copyShort(result, a);
    } else if (classType == char.class) {
      copyChar(result, a);
    } else if (classType == byte.class) {
      copyByte(result, a);
    } else if (classType == boolean.class) {
      copyBoolean(result, a);
    } else
      copyObject(result, a);
  }

  /**
   * copy array a to array result as doubles
   * The values from the arrays a are converted to double (if needed),
   * and then converted to the type of result (if needed).
   *
   * @param result copy to here
   * @param a copy from here
   *
   * @throws IllegalArgumentException a and result are not conformable
   */
  public static void copyDouble(Array result, Array a) throws IllegalArgumentException {
    if (!conformable(a, result))
      throw new IllegalArgumentException("copy arrays are not conformable");

    IndexIterator iterA = a.getIndexIterator();
    IndexIterator iterR = result.getIndexIterator();
    while (iterA.hasNext())
      iterR.setDoubleNext(iterA.getDoubleNext());
  }

  /**
   * copy array a to array result as floats
   * The values from the arrays a are converted to float (if needed),
   * and then converted to the type of result (if needed).
   *
   * @param result copy to here
   * @param a copy from here
   *
   * @throws IllegalArgumentException a and result are not conformable
   */
  public static void copyFloat(Array result, Array a) throws IllegalArgumentException {
    if (!conformable(a, result))
      throw new IllegalArgumentException("copy arrays are not conformable");

    IndexIterator iterA = a.getIndexIterator();
    IndexIterator iterR = result.getIndexIterator();
    while (iterA.hasNext())
      iterR.setFloatNext(iterA.getFloatNext());
  }

  /**
   * copy array a to array result as longs
   * The values from the array a are converted to long (if needed),
   * and then converted to the type of result (if needed).
   * @param result copy to here
   * @param a copy from here
   *
   *
   * @throws IllegalArgumentException a and result are not conformable
   */
  public static void copyLong(Array result, Array a) throws IllegalArgumentException {
    if (!conformable(a, result))
      throw new IllegalArgumentException("copy arrays are not conformable");

    IndexIterator iterA = a.getIndexIterator();
    IndexIterator iterR = result.getIndexIterator();
    while (iterA.hasNext())
      iterR.setLongNext(iterA.getLongNext());
  }

  /**
   * copy array a to array result as integers
   * The values from the arrays a are converted to integer (if needed),
   * and then converted to the type of result (if needed).
   *
   * @param result copy to here
   * @param a copy from here
   *
   * @throws IllegalArgumentException a and result are not conformable
   */
  public static void copyInt(Array result, Array a) throws IllegalArgumentException {
    if (!conformable(a, result))
      throw new IllegalArgumentException("copy arrays are not conformable");

    IndexIterator iterA = a.getIndexIterator();
    IndexIterator iterR = result.getIndexIterator();
    while (iterA.hasNext())
      iterR.setIntNext(iterA.getIntNext());
  }

  /**
   * copy array a to array result as shorts
   * The values from the array a are converted to short (if needed),
   * and then converted to the type of result (if needed).
   *
   * @param result copy to here
   * @param a copy from here
   *
   * @throws IllegalArgumentException a and result are not conformable
   */
  public static void copyShort(Array result, Array a) throws IllegalArgumentException {
    if (!conformable(a, result))
      throw new IllegalArgumentException("copy arrays are not conformable");

    IndexIterator iterA = a.getIndexIterator();
    IndexIterator iterR = result.getIndexIterator();
    while (iterA.hasNext())
      iterR.setShortNext(iterA.getShortNext());
  }

  /**
   * copy array a to array result as char
   * The values from the array a are converted to char (if needed),
   * and then converted to the type of result (if needed).
   *
   * @param result copy to here
   * @param a copy from here
   *
   * @throws IllegalArgumentException a and result are not conformable
   */
  public static void copyChar(Array result, Array a) throws IllegalArgumentException {
    if (!conformable(a, result))
      throw new IllegalArgumentException("copy arrays are not conformable");

    IndexIterator iterA = a.getIndexIterator();
    IndexIterator iterR = result.getIndexIterator();
    while (iterA.hasNext())
      iterR.setCharNext(iterA.getCharNext());
  }


  /**
   * copy array a to array result as bytes
   * The values from the array a are converted to byte (if needed),
   * and then converted to the type of result (if needed).
   *
   * @param result copy to here
   * @param a copy from here
   *
   * @throws IllegalArgumentException a and result are not conformable
   */
  public static void copyByte(Array result, Array a) throws IllegalArgumentException {
    if (!conformable(a, result))
      throw new IllegalArgumentException("copy arrays are not conformable");

    IndexIterator iterA = a.getIndexIterator();
    IndexIterator iterR = result.getIndexIterator();
    while (iterA.hasNext())
      iterR.setByteNext(iterA.getByteNext());
  }

  /**
   * copy array a to array result as bytes
   * The array a and result must be type boolean
   *
   * @param result copy to here
   * @param a copy from here
   *
   * @throws IllegalArgumentException a and result are not conformable
   */
  public static void copyBoolean(Array result, Array a) throws IllegalArgumentException {
    if (!conformable(a, result))
      throw new IllegalArgumentException("copy arrays are not conformable");

    IndexIterator iterA = a.getIndexIterator();
    IndexIterator iterR = result.getIndexIterator();
    while (iterA.hasNext())
      iterR.setBooleanNext(iterA.getBooleanNext());
  }

  /**
   * copy array a to array result as an Object
   * The array a and result must be type object
   *
   * @param result copy to here
   * @param a copy from here
   *
   * @throws IllegalArgumentException a and result are not conformable
   */
  public static void copyObject(Array result, Array a) throws IllegalArgumentException {
    if (!conformable(a, result))
      throw new IllegalArgumentException("copy arrays are not conformable");

    IndexIterator iterA = a.getIndexIterator();
    IndexIterator iterR = result.getIndexIterator();
    while (iterA.hasNext()) {
      iterR.setObjectNext( iterA.getObjectNext());
    }
  }

  /**
   * Calculate the reduced rank of this shape, by subtracting dimensions with length 1
   * @param shape shape of the array
   * @return rank without dimensions of length 1
   */
  public static int reducedRank(int[] shape) {
    int rank = 0;
    for (int aShape : shape) {
      if (aShape > 1)
        rank++;
    }
    return rank;
  }

  public static double getMinimum(Array a) {
    IndexIterator iter = a.getIndexIterator();
    double min = Double.MAX_VALUE;
    while (iter.hasNext()) {
      double val = iter.getDoubleNext();
      if (Double.isNaN(val)) continue;
      if (val < min)
        min = val;
    }
    return min;
  }

  public static double getMaximum(Array a) {
    IndexIterator iter = a.getIndexIterator();
    double max = -Double.MAX_VALUE;
    while (iter.hasNext()) {
      double val = iter.getDoubleNext();
      if (Double.isNaN(val)) continue;
      if (val > max)
        max = val;
    }
    return max;
  }

  /**
   * Find min and max value in this array, getting values as doubles. Skip Double.NaN.
   *
   * @param a the array.
   * @return MinMax
   */
  public static MAMath.MinMax getMinMax(Array a) {
    IndexIterator iter = a.getIndexIterator();
    double max = -Double.MAX_VALUE;
    double min = Double.MAX_VALUE;
    while (iter.hasNext()) {
      double val = iter.getDoubleNext();
      if (Double.isNaN(val)) continue;
      if (val > max)
        max = val;
      if (val < min)
        min = val;
    }
    return new MinMax(min, max);
  }

  public static MAMath.MinMax getMinMaxSkipMissingData(Array a, IsMissingEvaluator eval) {
    if (eval == null || !eval.hasMissing())
      return MAMath.getMinMax(a);

    IndexIterator iter = a.getIndexIterator();
    double max = -Double.MAX_VALUE;
    double min = Double.MAX_VALUE;
    while (iter.hasNext()) {
      double val = iter.getDoubleNext();
      if (eval.isMissing(val))
        continue;
      if (val > max)
        max = val;
      if (val < min)
        min = val;
    }
    return new MAMath.MinMax(min, max);
  }


  public static double getMinimumSkipMissingData(Array a, double missingValue) {
    IndexIterator iter = a.getIndexIterator();
    double min = Double.MAX_VALUE;
    while (iter.hasNext()) {
      double val = iter.getDoubleNext();
      if ((val != missingValue) && (val < min))
        min = val;
    }
    return min;
  }

  public static double getMaximumSkipMissingData(Array a, double missingValue) {
    IndexIterator iter = a.getIndexIterator();
    double max = -Double.MAX_VALUE;
    while (iter.hasNext()) {
      double val = iter.getDoubleNext();
      if ((val != missingValue) && (val > max))
        max = val;
    }
    return max;
  }

  public static MAMath.MinMax getMinMaxSkipMissingData(Array a, double missingValue) {
    IndexIterator iter = a.getIndexIterator();
    double max = -Double.MAX_VALUE;
    double min = Double.MAX_VALUE;
    while (iter.hasNext()) {
      double val = iter.getDoubleNext();
      if (val == missingValue)
        continue;
      if (val > max)
        max = val;
      if (val < min)
        min = val;
    }
    return new MinMax(min, max);
  }


  /**
   * Set all the elements of this array to the given double value.
   * The value is converted to the element type of the array, if needed.
   *
   * @param result change this Array
   * @param val set all elements to this value
   */
  public static void setDouble(Array result, double val) {
    IndexIterator iter = result.getIndexIterator();
    while (iter.hasNext()) {
      iter.setDoubleNext(val);
    }
  }

  /**
   * sum all of the elements of array a as doubles.
   * The values from the array a are converted to double (if needed).
   * @param a read values from this Array
   * @return sum of elements
   */
  public static double sumDouble(Array a) {
    double sum = 0;
    IndexIterator iterA = a.getIndexIterator();
    while (iterA.hasNext()) {
      sum += iterA.getDoubleNext();
    }
    return sum;
  }

  /**
   * sum all of the elements of array a as doubles.
   * The values from the array a are converted to double (if needed).
   * @param a read values from this Array
   * @param missingValue skip values equal to this, or which are NaNs
   * @return sum of elements
   */
  public static double sumDoubleSkipMissingData(Array a, double missingValue) {
    double sum = 0;
    IndexIterator iterA = a.getIndexIterator();
    while (iterA.hasNext()) {
      double val = iterA.getDoubleNext();
      if ((val == missingValue) || Double.isNaN(val))
        continue;
      sum += val;
    }
    return sum;
  }

  /**
   * Holds a minimum and maximum value.
   */
  public static class MinMax {
    public double min, max;

    public MinMax(double min, double max) {
      this.min = min;
      this.max = max;
    }

    @Override
    public String toString() {
      return "MinMax{" +
              "min=" + min +
              ", max=" + max +
              '}';
    }
  }

  /**
   * Calculate the scale/offset for an array of numbers.
   * 
   * If signed:
   *   then
   *     max value unpacked = 2^(n-1) - 1 packed
   *     min value unpacked = -(2^(n-1) - 1) packed
   *   note that -2^(n-1) is unused, and a good place to map missing values
   *   by solving 2 eq in 2 unknowns, we get:
   *     scale = (max - min) / (2^n - 2)
   *     offset = (max + min) / 2
   * If unsigned then
   *     max value unpacked = 2^n - 1 packed
   *     min value unpacked = 0 packed
   *   and:
   *     scale = (max - min) / (2^n - 1)
   *     offset = min
   *   One could modify this to allow a holder for missing values.
   * 
* @param a array to convert (not changed) * @param missingValue skip these * @param nbits map into this many bits * @return ScaleOffset, calculated as above. */ public static MAMath.ScaleOffset calcScaleOffsetSkipMissingData(Array a, double missingValue, int nbits) { MAMath.MinMax minmax = getMinMaxSkipMissingData(a, missingValue); if (a.isUnsigned()) { long size = (1L << nbits) - 1; double offset = minmax.min; double scale =(minmax.max - minmax.min) / size; return new ScaleOffset(scale, offset); } else { long size = (1L << nbits) - 2; double offset = (minmax.max + minmax.min) / 2; double scale =(minmax.max - minmax.min) / size; return new ScaleOffset(scale, offset); } } public static Array convert2packed(Array unpacked, double missingValue, int nbits, DataType packedType) { MAMath.ScaleOffset scaleOffset = calcScaleOffsetSkipMissingData(unpacked, missingValue, nbits); Array result = Array.factory(packedType, unpacked.getShape()); IndexIterator riter = result.getIndexIterator(); while (unpacked.hasNext()) { double uv = unpacked.nextDouble(); double pv = (uv - scaleOffset.offset) / scaleOffset.scale; riter.setDoubleNext( pv); } return result; } public static Array convert2Unpacked(Array packed, ScaleOffset scaleOffset) { Array result = Array.factory(DataType.DOUBLE, packed.getShape()); IndexIterator riter = result.getIndexIterator(); while (packed.hasNext()) { riter.setDoubleNext( packed.nextDouble() * scaleOffset.scale + scaleOffset.offset); } return result; } /** * Holds a scale and offset. */ public static class ScaleOffset { public double scale, offset; public ScaleOffset(double scale, double offset) { this.scale = scale; this.offset = offset; } } /** * Returns true if the specified arrays have the same size, signedness, and approximately equal corresponding * elements. {@code float} elements must be within {@link Misc#defaultMaxRelativeDiffFloat} of each other, as * determined by {@link Misc#nearlyEquals(double, double, double)}. Similarly, {@code double} elements must be within * {@link Misc#defaultMaxRelativeDiffDouble} of each other. *

* {@link #equals(Array, Array)} is an alternative to this method that requires that corresponding elements be * exactly equal. It is suitable for use in {@link Object#equals} implementations, whereas this method isn't. * * @param data1 one array to be tested for equality. * @param data2 the other array to be tested for equality. * @return true if the specified arrays have the same size, signedness, and approximately equal corresponding elems. */ public static boolean nearlyEquals(Array data1, Array data2) { if (data1 == data2) { // Covers case when both are null. return true; } else if (data1 == null || data2 == null) { return false; } if (data1.getSize() != data2.getSize()) return false; if (data1.isUnsigned() != data2.isUnsigned()) return false; DataType dt = DataType.getType(data1); IndexIterator iter1 = data1.getIndexIterator(); IndexIterator iter2 = data2.getIndexIterator(); if (dt == DataType.DOUBLE) { while (iter1.hasNext() && iter2.hasNext()) { double v1 = iter1.getDoubleNext(); double v2 = iter2.getDoubleNext(); if (!Misc.nearlyEquals(v1, v2, Misc.defaultMaxRelativeDiffDouble)) return false; } } else if (dt == DataType.FLOAT) { while (iter1.hasNext() && iter2.hasNext()) { float v1 = iter1.getFloatNext(); float v2 = iter2.getFloatNext(); if (!Misc.nearlyEquals(v1, v2, Misc.defaultMaxRelativeDiffFloat)) return false; } } else if (dt.getPrimitiveClassType() == int.class) { while (iter1.hasNext() && iter2.hasNext()) { int v1 = iter1.getIntNext(); int v2 = iter2.getIntNext(); if (v1 != v2) return false; } } else if (dt.getPrimitiveClassType() == byte.class) { while (iter1.hasNext() && iter2.hasNext()) { short v1 = iter1.getShortNext(); short v2 = iter2.getShortNext(); if (v1 != v2) return false; } } else if (dt.getPrimitiveClassType() == short.class) { while (iter1.hasNext() && iter2.hasNext()) { byte v1 = iter1.getByteNext(); byte v2 = iter2.getByteNext(); if (v1 != v2) return false; } } else if (dt.getPrimitiveClassType() == long.class) { while (iter1.hasNext() && iter2.hasNext()) { long v1 = iter1.getLongNext(); long v2 = iter2.getLongNext(); if (v1 != v2) return false; } } else { while (iter1.hasNext() && iter2.hasNext()) { if (!Objects.equals(iter1.next(), iter2.next())) { return false; } } } return true; } /** * Returns true if the specified arrays have the same data type, shape, and equal corresponding elements. This method * is suitable for use in {@link Object#equals} implementations. *

* Note that floating-point elements must be exactly equal, not merely within some epsilon of each other. This is * because it's impossible to write a strictly-conforming {@link Object#equals} implementation when an * epsilon is incorporated, due to the transitivity requirement. *

* {@link #nearlyEquals} is an alternative to this method that returns true if the corresponding elements are * "approximately" equal to each other. * * @param array1 one array to be tested for equality. * @param array2 the other array to be tested for equality. * @return true if the specified arrays have the same data type, shape, and equal corresponding elements. * @see Is there a way to get a hashcode of a float with epsilon? - Stack Overflow */ // TODO: Should we add this to Array as the Object.equals() implementation? How much work is that? public static boolean equals(Array array1, Array array2) { if (array1 == array2) { // Covers case when both are null. return true; } else if (array1 == null || array2 == null) { return false; } // MAMath.nearlyEquals() does not require DataTypes to be equal, but in so doing, it becomes non-symmetric. // For example, suppose we have 2 arrays: // ArrayLong al; // ArrayShort as; // If al contains elements that don't fit in a short, we could have the following: // MAMath.nearlyEquals(al, as); // true // MAMath.nearlyEquals(as, al); // false // This is because when MAMath.nearlyEquals() does comparisons, elements from the 2nd array are converted to the // type of the 1st array. // // In our implementation, we avoid this problem--and thus preserve symmetry--by insisting that the element // types of the 2 arrays are the same. This means that we lose some of the "flexibility" offered by the MAMath // version, but it turns out that it's not a good idea to compare different Number subtypes in the first place: // http://stackoverflow.com/questions/480632/why-doesnt-java-lang-number-implement-comparable if (array1.getDataType() != array2.getDataType()) { return false; } // MAMath.nearlyEquals() only requires that the 2 arrays have the same size, not the same shape. That was an // option I considered. Also, there's MAMath.conformable(), which returns true if shapes are equal after // reduction (e.g. { 3,4,5 } and { 3,1,4,1,5 } are conformable). By definition, conformable arrays have // the same size. // // Ultimately, I decided on the stricter requirement that the arrays must have the exact same shape. That's // because if 2 objects compare as equal, there's a general expectation that you can perform the same // operations on them and get the same result. But imagine this: // Array a1 = Array.factory(DataType.INT, new int[] { 3,4 }, new int[] { 0,1,2,3,4,5,6,7,8,9,10,11 }); // Array a2 = Array.factory(DataType.INT, new int[] { 2,6 }, new int[] { 0,1,2,3,4,5,6,7,8,9,10,11 }); // MAMath.nearlyEquals() will consider the arrays equal because they have the same size, but: // a1.getInt(a1.getIndex().set(1, 1)) == 5 // a2.getInt(a2.getIndex().set(1, 1)) == 7 if (!Arrays.equals(array1.getShape(), array2.getShape())) { return false; } // Note that we did not examine the Indexes of the arrays, nor their 1D backing stores. That's because we're // interested in the VIEWS of the data that the Arrays present, not with how the data is manipulated behind the // scenes. IndexIterator iter1 = array1.getIndexIterator(); IndexIterator iter2 = array2.getIndexIterator(); while (iter1.hasNext() && iter2.hasNext()) { if (!Objects.equals(iter1.next(), iter2.next())) { return false; } if (!Arrays.equals(iter1.getCurrentCounter(), iter2.getCurrentCounter())) { return false; } } assert !iter1.hasNext() && !iter2.hasNext(); // Iterators ought to be the same length. return true; } /** * An implementation of {@link Object#hashCode} that is consistent with {@link #equals(Array, Array)}. * * @param array an array to hash. * @return a hash code value for the array. */ // TODO: Should we add this to Array as the Object.hashCode() implementation? How much work is that? public static int hashCode(Array array) { if (array == null) { return 0; } int hash = 3; hash = 29 * hash + array.getDataType().hashCode(); hash = 29 * hash + Arrays.hashCode(array.getShape()); // We can't simply hash array.getStorage(), because array may be a "view" that doesn't include all of the // elements in the backing store. for (IndexIterator iter = array.getIndexIterator(); iter.hasNext(); ) { hash = 29 * hash + iter.next().hashCode(); hash = 29 * hash + Arrays.hashCode(iter.getCurrentCounter()); } return hash; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy