/******************************************************************************* * Copyright 2014 Analog Devices, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ package com.analog.lyric.collect; import java.lang.reflect.Array; import java.util.Collection; import java.util.Comparator; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import com.analog.lyric.util.misc.Internal; import com.google.common.math.DoubleMath; import cern.colt.list.IntArrayList; /** * Contains static utility methods pertaining to arrays. */ public abstract class ArrayUtil { /** * Canonical empty boolean array. */ public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; /** * Canonical empty Class array. * @since 0.07 */ public static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0]; /** * Canonical empty double array. */ public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; /** * Canonical empty double[][]. * @since 0.06 */ public static final double[][] EMPTY_DOUBLE_ARRAY_ARRAY = new double[0][]; /** * Canonical empty Object array. * * @since 0.06 */ public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; /** * Canonical empty Object array. * * @since 0.06 */ public static final Object[][] EMPTY_OBJECT_ARRAY_ARRAY = new Object[0][]; /** * Canonical empty int array. */ public static final int[] EMPTY_INT_ARRAY = new int[0]; /** * Canonical empty int[][]. */ public static final int[][] EMPTY_INT_ARRAY_ARRAY = new int[0][]; /** * Canonical empty String[] * @since 0.07 */ public static final String[] EMPTY_STRING_ARRAY = new String[0]; /** * Returns an array with length at least {@code minSize} and with component type * compatible with {@code type}. * <p> * If {@code array} fits the above description, it will simply be returned. * If {@code array} is too short but has a compatible component type, this will * return a new array with length equal to {@code minSize} and component type * the same as {@code array}. Otherwise returns a new array with component * type same as {@code type}. */ public static <T> T[] allocateArrayOfType(Class<?> type, @Nullable T[] array, int minSize) { if (array != null) { Class<?> componentType = array.getClass().getComponentType(); if (componentType.isAssignableFrom(type)) { if (array.length >= minSize) { return array; } else { type = componentType; } } } @SuppressWarnings("unchecked") T[] result = (T[])Array.newInstance(type, minSize); return result; } /** * True if all values in {@code array} are {@link DoubleMath#fuzzyEquals} * to each other with given {@code tolerance}. Returns true for arrays * of less than length 2. * * @see #subsetFuzzyEqual(double[], int[], double) */ public static boolean allFuzzyEqual(double[] array, double tolerance) { if (array.length > 1) { double min , max; min = max = array[0]; for (int i = 1, end = array.length; i < end ; ++i) { final double d = array[i]; min = Math.min(min, d); max = Math.max(max, d); } if (!DoubleMath.fuzzyEquals(min, max, tolerance)) { return false; } } return true; } /** * Given an array of non-negative array indices in ascending order, and another sorted list * of indices of entries to be removed, this returns a new array containing the new values of * the remaining indices * <p> * For example, given the original list [0, 1, 3] and the removed list [1,2], this will * return [0,1]. * <p> * @param list is the starting list of non-negative indices in ascending order. * @param remove is a non-empty list of indexes to be removed also in ascending order. * @return a newly allocated array. * @since 0.05 */ public static int[] contractSortedIndexList(int[] list, int[] remove) { final int originalLength = list.length; final int excludeLength = remove.length; final IntArrayList result = new IntArrayList(originalLength); int iConst = 0; int iList = 0; int listIndex; int constantIndex = remove[iConst]; while (iList < originalLength) { listIndex = list[iList]; if (iConst < excludeLength) constantIndex = remove[iConst]; if (listIndex == constantIndex) { // Skip this list index entry iList++; } else if (listIndex < constantIndex || iConst >= excludeLength) { // Add this entry result.add(listIndex - iConst); iList++; } else if (listIndex > constantIndex) { // Move to the next constant if there is one iConst++; } } // Convert contracted list back to an int[] result.trimToSize(); return result.elements(); } /** * Copies contents of collection into a new array with given component type. * @since 0.05 */ public static <T> T[] copy(Class<T> componentType, Collection<T> collection) { @SuppressWarnings("unchecked") final T[] array = (T[]) Array.newInstance(componentType, collection.size()); return collection.toArray(array); } /** * Copies entries from {@code source} array at specified {@code sourceIndices} to new array. * * @param source is the array from which entries will be copied. * @param sourceIndices specifies which entries to copy into new array in which order. * Each index value must be in the range [0,{@code source.length}-1] and indicates which entry is to be * copied into the corresponding destination. * @return new array * * @see #copyFromIndices(Object[], int[], Object[]) * @since 0.05 */ public static <T> T[] copyFromIndices(T[] source, int[] sourceIndices) { return copyFromIndices(source, sourceIndices, null); } /** * Copies entries from {@code source} array at specified {@code sourceIndices} to {@code destination} array. * * @param source is the array from which entries will be copied. * @param sourceIndices must be the same length as {@code destination} array (if not null). Each index value * must be in the range [0,{@code source.length}-1] and indicates which entry is to be copied into the * corresponding destination. * @param destination is the array into which the entries are to be copied. If null, then a new one * will be allocated using same type as {@code source} and length matching that of {@code sourceIndices}. * @return array into which values have been copied. Will be same as {@code destination} if not null. * * @see #copyFromIndices(Object[], int[]) * @since 0.05 */ public static <T> T[] copyFromIndices(T[] source, int[] sourceIndices, @Nullable T[] destination) { final int destSize = sourceIndices.length; @SuppressWarnings("unchecked") @NonNull T[] result = destination != null ? destination : (T[])Array.newInstance(source.getClass().getComponentType(), destSize); for (int to = 0; to < destSize; ++to) { result[to] = source[sourceIndices[to]]; } return result; } /** * Searches array in linear order for first element with matching key. * * @param array the array to be searched * @param key the value to be searched for * @param fromIndex the index of the first element (inclusive) to be searched * @param toIndex the index of the last element (exclusive) to be searched * * @return index of first array element in range for which {@code key.equals(array[i].getKey())}; otherwise -1. * * @since 0.06 */ public static <K> int linearSearch(IKeyed<K>[] array, K key, int fromIndex, int toIndex) { for (int i = fromIndex; i < toIndex; ++i) { if (key.equals(array[i].getKey())) { return i; } } return -1; } /** * Searches array in linear order for first element with matching key. * * @param array the array to be searched * @param key the value to be searched for * * @return index of first array element for which {@code key.equals(array[i].getKey())}; otherwise -1. * * @since 0.06 */ public static <K> int linearSearch(IKeyed<K>[] array, K key) { return linearSearch(array, key, 0, array.length); } /** * Determines if array is ordered according to provided comparator. * @since 0.05 */ public static <T> boolean isSorted(T[] array, Comparator<T> comparator) { final int size = array.length; if (size > 1) { T prev = array[0]; for (int i = 1; i < size; ++i) { final T next = array[i]; if (0 < comparator.compare(prev, next)) { return false; } prev = next; } } return true; } /** * Determines if array is ordered according to elements' {@link Comparable#compareTo(Object)} method. * @since 0.05 */ public static <T extends Comparable<T>> boolean isSorted(T[] array) { final int size = array.length; if (size > 1) { T prev = array[0]; for (int i = 1; i < size; ++i) { final T next = array[i]; if (0 < prev.compareTo(next)) { return false; } prev = next; } } return true; } /** * True if array is non-empty and contains only the specified element. * @since 0.08 */ public static boolean onlyContains(double[] array, double element) { for (double x : array) if (x != element) return false; return array.length > 0; } /** * True if all values in {@code array} whose indices are in {@code arraySubindices} * are {@link DoubleMath#fuzzyEquals} to each other with given {@code tolerance}. Returns true if * length of {@code arraySubindices} is less than length 2. * * @param arraySubindices must be no larger than {@code array} and should contain indexes in the * range [0,array.length-1]. * * @see #allFuzzyEqual(double[], double) */ public static boolean subsetFuzzyEqual(double[] array, int[] arraySubindices, double tolerance) { if (arraySubindices.length > 1) { double min , max; min = max = array[arraySubindices[0]]; for (int i = 1, end = arraySubindices.length; i < end ; ++i) { final double d = array[arraySubindices[i]]; min = Math.min(min, d); max = Math.max(max, d); } if (!DoubleMath.fuzzyEquals(min, max, tolerance)) { return false; } } return true; } /** * Returns a copy of {@code array} returning null if {@code array} * is null and returns {@link #EMPTY_DOUBLE_ARRAY} if empty. */ public static @Nullable double[] cloneArray(@Nullable double[] array) { if (array == null) { return null; } else if (array.length == 0) { return EMPTY_DOUBLE_ARRAY; } else { return array.clone(); } } /** * Returns a copy of {@code array} or {@link #EMPTY_DOUBLE_ARRAY} if empty. * @since 0.08 */ public static double[] cloneNonNullArray(double[] array) { return array.length == 0 ? EMPTY_DOUBLE_ARRAY : array.clone(); } /** * Returns a copy of {@code array} returning null if {@code array} * is null and returns {@link #EMPTY_INT_ARRAY} if empty. */ public static @Nullable int[] cloneArray(@Nullable int[] array) { if (array == null) { return null; } else if (array.length == 0) { return EMPTY_INT_ARRAY; } else { return array.clone(); } } /** * Returns a copy of {@code array} returning null if {@code array} * is null and returns {@link #EMPTY_INT_ARRAY_ARRAY} if empty. * Note that this does not make a deep copy of the array elements. */ public static @Nullable int[][] cloneArray(@Nullable int[][] array) { if (array == null) { return null; } else if (array.length == 0) { return EMPTY_INT_ARRAY_ARRAY; } else { return array.clone(); } } /** * Returns a copy of {@code array} returning null if {@code array} * is null and returns {@link #EMPTY_DOUBLE_ARRAY_ARRAY} if empty. * Note that this does not make a deep copy of the array elements. * * @since 0.08 * @see #deepCloneArray(double[][]) */ public static @Nullable double[][] cloneArray(@Nullable double[][] array) { if (array == null) { return null; } else if (array.length == 0) { return EMPTY_DOUBLE_ARRAY_ARRAY; } else { return array.clone(); } } /** * Returns a copy of {@code array} but with space for insertion of {@code insertLength} * values at offset {@code insertionPoint}. * @throws ArrayIndexOutOfBoundsException if {@code insertionPoint} is not in the range * [0, array.length]. */ public static double[] copyArrayForInsert(@Nullable double[] array, int insertionPoint, int insertLength) { if (array == null) { return new double[insertLength]; } int curSize = array.length; double[] newArray = new double[curSize + insertLength]; System.arraycopy(array, 0, newArray, 0, insertionPoint); System.arraycopy(array, insertionPoint, newArray, insertionPoint + insertLength, curSize - insertionPoint); return newArray; } /** * Returns a copy of {@code array} but with space for insertion of {@code insertLength} * values at offset {@code insertionPoint}. * @throws ArrayIndexOutOfBoundsException if {@code insertionPoint} is not in the range * [0, array.length]. */ public static int[] copyArrayForInsert(@Nullable int[] array, int insertionPoint, int insertLength) { if (array == null) { return new int[insertLength]; } int curSize = array.length; int[] newArray = new int[curSize + insertLength]; System.arraycopy(array, 0, newArray, 0, insertionPoint); System.arraycopy(array, insertionPoint, newArray, insertionPoint + insertLength, curSize - insertionPoint); return newArray; } /** * Returns a copy of {@code array} but with space for insertion of {@code insertLength} * values at offset {@code insertionPoint}. Note that this does not make a deep copy of the array elements. * @throws ArrayIndexOutOfBoundsException if {@code insertionPoint} is not in the range * [0, array.length]. */ public static int[][] copyArrayForInsert(@Nullable int[][] array, int insertionPoint, int insertLength) { if (array == null) { return new int[insertLength][]; } int curSize = array.length; int[][] newArray = new int[curSize + insertLength][]; System.arraycopy(array, 0, newArray, 0, insertionPoint); System.arraycopy(array, insertionPoint, newArray, insertionPoint + insertLength, curSize - insertionPoint); return newArray; } /** * Returns a deep copy of {@code array} returning null if {@code array} * is null and returns {@link #EMPTY_DOUBLE_ARRAY_ARRAY} if empty. * * @since 0.08 * @see #cloneArray(double[][]) */ public static @Nullable double[][] deepCloneArray(@Nullable double[][] array) { if (array == null) { return null; } else if (array.length == 0) { return EMPTY_DOUBLE_ARRAY_ARRAY; } else { final int n = array.length; double[][] clone = new double[n][]; for (int i = 0; i < n; ++i) { clone[i] = array[i].clone(); } return clone; } } public static @Nullable Object[] toArray(@Nullable Object value) { if (value instanceof Object[]) { return (Object[])value; } if (value != null && value.getClass().isArray()) { final int size = Array.getLength(value); final Object[] array = new Object[size]; for (int i = 0; i < size; ++i) { array[i] = Array.get(value, i); } return array; } return null; } /** * Converts value to an int array or else returns null. * <p> * If {@code value} is an int array it will simply be returned. * If it is another type of array, whose elements are * all integral values, then a new array with those values will * be returned. Otherwise returns null. */ public static @Nullable int[] toIntArray(@Nullable Object value) { if (value instanceof int[]) { return (int[])value; } if (value != null && value.getClass().isArray()) { final int size = Array.getLength(value); final int[] array = new int[size]; for (int i = 0; i < size; ++i) { Object obj = Array.get(value, i); if (obj instanceof Number) { Number number = (Number)obj; int index = number.intValue(); if (index == number.doubleValue()) { array[i] = index; continue; } } return null; } return array; } return null; } /** * Returns a copy of an int array, without the element at the specified index. * * @param array The original array. * @param index The zero-based index of the element to exclude from the copy. * @since 0.06 */ @Internal public static int[] removeIntArrayEntry(final int[] array, final int index) { final int[] result = new int[array.length - 1]; System.arraycopy(array, 0, result, 0, index); System.arraycopy(array, index + 1, result, index, result.length - index); return result; } }