diff --git a/src/main/java/org/apache/commons/lang3/ArrayUtils.java b/src/main/java/org/apache/commons/lang3/ArrayUtils.java index a571636e074..e3b49e89e2a 100644 --- a/src/main/java/org/apache/commons/lang3/ArrayUtils.java +++ b/src/main/java/org/apache/commons/lang3/ArrayUtils.java @@ -1666,6 +1666,1106 @@ public static boolean contains(final short[] array, final short valueToFind) { return indexOf(array, valueToFind) != INDEX_NOT_FOUND; } + /** + * Searches the specified array for the ceiling of the specified object using binary search algorithm. + * The ceiling of a comparable object is the least element in the array that is greater than or equal to the object. + * The input array must be sorted in ascending order according to the {@linkplain Comparable natural ordering} + * of its element. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param valueToFind A comparable object for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found. + * @throws ClassCastException If the array elements or valueToFind is not comparable + * @throws NullPointerException If array is null or comparing null values is not allowed + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static int ceiling(final Object[] array, final Object valueToFind) { + return ceiling(array, 0, array.length, valueToFind, Comparator.comparing(v -> ((Comparable) v))); + } + + /** + * Searches the specified array for the ceiling of the specified object using binary search algorithm within + * a specified range. + * The ceiling of a comparable object is the least element within the range that is greater than or equal to + * the object. + * The input array must be sorted in ascending order according to the {@linkplain Comparable natural ordering} + * of its element within the specified range (other array elements are not checked). + * Passing an array that is unsorted within the specified range (or an array that is sorted in descending order + * within that range) will lead to undefined behavior. + * The range is exclusive from the right end. + * + * @param the class of the objects in the array + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value A comparable object for which we want to find a ceiling + * @param comparator The comparator by which the array is ordered. + * A {@code null} value indicates that the elements' {@linkplain Comparable natural ordering} + * should be used. + * @return The index of the ceiling if it is found; -1 will be returned if no ceiling was found within the + * specified range. + * @throws ClassCastException If the array elements or value is not comparable + * @throws NullPointerException If array is null or comparing null values is not allowed + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int ceiling(final T[] array, + final int fromIndex, + final int toIndex, + final T value, + final Comparator comparator) { + if (comparator == null) { + return ceiling(array, fromIndex, toIndex, value, + (v1, v2) -> ((Comparable) v1).compareTo((Comparable) v2)); + } + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (comparator.compare(value, array[toIndex - 1]) > 0) { + return INDEX_NOT_FOUND; + } + if (comparator.compare(value, array[fromIndex]) <= 0) { + return fromIndex; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + final int cmp = comparator.compare(array[mid], value); + if (cmp == 0) { + return mid; + } else if (cmp > 0) { + if (fromIndex <= mid - 1 && comparator.compare(array[mid - 1], value) < 0) { + return mid; + } + return ceiling(array, fromIndex, mid, value, comparator); + } + if (mid + 1 <= toIndex - 1 && comparator.compare(array[mid + 1], value) >= 0) { + return mid + 1; + } + return ceiling(array, mid + 1, toIndex, value, comparator); + } + + /** + * Searches the specified array for the ceiling of the specified int value using binary search + * algorithm. + * The ceiling of an int value is the least element in the array that is greater than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The int value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found. + * @throws NullPointerException If array is null + */ + public static int ceiling(final int[] array, final int value) { + return ceiling(array, 0, array.length, value); + } + + /** + * Searches the specified array for the ceiling of the specified int value using binary search + * algorithm within the specified range. + * The ceiling of an int value within a range is the least element within the range that is greater + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The int value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int ceiling(final int[] array, + final int fromIndex, + final int toIndex, + final int value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (value > array[toIndex - 1]) { + return INDEX_NOT_FOUND; + } + if (value <= array[fromIndex]) { + return fromIndex; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] < value) { + return mid; + } + return ceiling(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] >= value) { + return mid + 1; + } + return ceiling(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the ceiling of the specified long value using binary search + * algorithm. + * The ceiling of an long value is the least element in the array that is greater than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The long value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found. + * @throws NullPointerException If array is null + */ + public static int ceiling(final long[] array, final long value) { + return ceiling(array, 0, array.length, value); + } + + /** + * Searches the specified array for the ceiling of the specified long value using binary search + * algorithm within the specified range. + * The ceiling of an long value within a range is the least element within the range that is greater + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The long value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int ceiling(final long[] array, + final int fromIndex, + final int toIndex, + final long value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (value > array[toIndex - 1]) { + return INDEX_NOT_FOUND; + } + if (value <= array[fromIndex]) { + return fromIndex; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] < value) { + return mid; + } + return ceiling(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] >= value) { + return mid + 1; + } + return ceiling(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the ceiling of the specified short value using binary search + * algorithm. + * The ceiling of an short value is the least element in the array that is greater than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The short value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found. + * @throws NullPointerException If array is null + */ + public static int ceiling(final short[] array, final short value) { + return ceiling(array, 0, array.length, value); + } + + /** + * Searches the specified array for the ceiling of the specified short value using binary search + * algorithm within the specified range. + * The ceiling of an short value within a range is the least element within the range that is greater + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The short value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int ceiling(final short[] array, + final int fromIndex, + final int toIndex, + final short value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (value > array[toIndex - 1]) { + return INDEX_NOT_FOUND; + } + if (value <= array[fromIndex]) { + return fromIndex; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] < value) { + return mid; + } + return ceiling(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] >= value) { + return mid + 1; + } + return ceiling(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the ceiling of the specified byte value using binary search + * algorithm. + * The ceiling of an byte value is the least element in the array that is greater than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The byte value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found. + * @throws NullPointerException If array is null + */ + public static int ceiling(final byte[] array, final byte value) { + return ceiling(array, 0, array.length, value); + } + + /** + * Searches the specified array for the ceiling of the specified byte value using binary search + * algorithm within the specified range. + * The ceiling of an byte value within a range is the least element within the range that is greater + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The byte value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int ceiling(final byte[] array, + final int fromIndex, + final int toIndex, + final byte value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (value > array[toIndex - 1]) { + return INDEX_NOT_FOUND; + } + if (value <= array[fromIndex]) { + return fromIndex; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] < value) { + return mid; + } + return ceiling(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] >= value) { + return mid + 1; + } + return ceiling(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the ceiling of the specified char value using binary search + * algorithm. + * The ceiling of an char value is the least element in the array that is greater than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The char value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found. + * @throws NullPointerException If array is null + */ + public static int ceiling(final char[] array, final char value) { + return ceiling(array, 0, array.length, value); + } + + /** + * Searches the specified array for the ceiling of the specified char value using binary search + * algorithm within the specified range. + * The ceiling of an char value within a range is the least element within the range that is greater + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The char value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int ceiling(final char[] array, + final int fromIndex, + final int toIndex, + final char value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (value > array[toIndex - 1]) { + return INDEX_NOT_FOUND; + } + if (value <= array[fromIndex]) { + return fromIndex; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] < value) { + return mid; + } + return ceiling(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] >= value) { + return mid + 1; + } + return ceiling(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the ceiling of the specified float value using binary search + * algorithm. + * The ceiling of an float value is the least element in the array that is greater than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The float value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found. + * @throws NullPointerException If array is null + */ + public static int ceiling(final float[] array, final float value) { + return ceiling(array, 0, array.length, value); + } + + /** + * Searches the specified array for the ceiling of the specified float value using binary search + * algorithm within the specified range. + * The ceiling of an float value within a range is the least element within the range that is greater + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The float value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int ceiling(final float[] array, + final int fromIndex, + final int toIndex, + final float value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (value > array[toIndex - 1]) { + return INDEX_NOT_FOUND; + } + if (value <= array[fromIndex]) { + return fromIndex; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] < value) { + return mid; + } + return ceiling(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] >= value) { + return mid + 1; + } + return ceiling(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the ceiling of the specified double value using binary search + * algorithm. + * The ceiling of an double value is the least element in the array that is greater than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The double value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found. + * @throws NullPointerException If array is null + */ + public static int ceiling(final double[] array, final double value) { + return ceiling(array, 0, array.length, value); + } + + /** + * Searches the specified array for the ceiling of the specified double value using binary search + * algorithm within the specified range. + * The ceiling of an double value within a range is the least element within the range that is greater + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The double value for which we want to find a ceiling + * @return The index of the ceiling if it is found. -1 will be returned if no ceiling was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int ceiling(final double[] array, + final int fromIndex, + final int toIndex, + final double value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (value > array[toIndex - 1]) { + return INDEX_NOT_FOUND; + } + if (value <= array[fromIndex]) { + return fromIndex; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] < value) { + return mid; + } + return ceiling(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] >= value) { + return mid + 1; + } + return ceiling(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the floor of the specified object using binary search algorithm. + * The floor of a comparable object is the greatest element in the array that is less than or equal to the object. + * The input array must be sorted in ascending order according to the {@linkplain Comparable natural ordering} + * of its element. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value A comparable object for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found. + * @throws ClassCastException If the array elements or value is not comparable + * @throws NullPointerException If array is null or comparing null values is not allowed + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static int floor(final Object[] array, final Object value) { + return floor(array, 0, array.length, value, Comparator.comparing(v -> ((Comparable) v))); + } + + /** + * Searches the specified array for the floor of the specified object using binary search algorithm within + * a specified range. + * The floor of a comparable object is the greatest element within the range that is less than or equal to + * the object. + * The input array must be sorted in ascending order according to the {@linkplain Comparable natural ordering} + * of its element within the specified range (other array elements are not checked). + * Passing an array that is unsorted within the specified range (or an array that is sorted in descending order + * within that range) will lead to undefined behavior. + * The range is exclusive from the right end. + * + * @param the class of the objects in the array + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value A comparable object for which we want to find a floor + * @param comparator The comparator by which the array is ordered. + * A {@code null} value indicates that the elements' {@linkplain Comparable natural ordering} + * should be used. + * @return The index of the floor if it is found; -1 will be returned if no floor was found within the + * specified range. + * @throws ClassCastException If the array elements or value is not comparable + * @throws NullPointerException If array is null or comparing null values is not allowed + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int floor(final T[] array, + final int fromIndex, + final int toIndex, + final T value, + final Comparator comparator) { + if (comparator == null) { + return ceiling(array, fromIndex, toIndex, value, + (v1, v2) -> ((Comparable) v1).compareTo((Comparable) v2)); + } + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (comparator.compare(array[fromIndex], value) > 0) { + return INDEX_NOT_FOUND; + } + if (comparator.compare(array[toIndex - 1], value) <= 0) { + return toIndex - 1; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + final int cmp = comparator.compare(array[mid], value); + if (cmp == 0) { + return mid; + } else if (cmp > 0) { + if (fromIndex <= mid - 1 && comparator.compare(array[mid - 1], value) <= 0) { + return mid - 1; + } + return floor(array, fromIndex, mid, value, comparator); + } + if (mid + 1 <= toIndex - 1 && comparator.compare(array[mid + 1], value) > 0) { + return mid; + } + return floor(array, mid + 1, toIndex, value, comparator); + } + + /** + * Searches the specified array for the floor of the specified int value using binary search + * algorithm. + * The floor of an int value is the greatest element in the array that is smaller than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The int value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found. + * @throws NullPointerException If array is null + */ + public static int floor(final int[] array, final int value) { + return floor(array, 0, array.length, value); + } + + /** + * Searches the specified array for the floor of the specified int value using binary search + * algorithm within the specified range. + * The floor of an int value within a range is the greatest element within the range that is smaller + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The int value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int floor(final int[] array, + final int fromIndex, + final int toIndex, + final int value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (array[fromIndex] > value) { + return INDEX_NOT_FOUND; + } + if (array[toIndex - 1] <= value) { + return toIndex - 1; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] <= value) { + return mid - 1; + } + return floor(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] > value) { + return mid; + } + return floor(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the floor of the specified long value using binary search + * algorithm. + * The floor of an long value is the greatest element in the array that is smaller than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The long value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found. + * @throws NullPointerException If array is null + */ + public static int floor(final long[] array, final long value) { + return floor(array, 0, array.length, value); + } + + /** + * Searches the specified array for the floor of the specified long value using binary search + * algorithm within the specified range. + * The floor of an long value within a range is the greatest element within the range that is smaller + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The long value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int floor(final long[] array, + final int fromIndex, + final int toIndex, + final long value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (array[fromIndex] > value) { + return INDEX_NOT_FOUND; + } + if (array[toIndex - 1] <= value) { + return toIndex - 1; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] <= value) { + return mid - 1; + } + return floor(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] > value) { + return mid; + } + return floor(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the floor of the specified float value using binary search + * algorithm. + * The floor of an float value is the greatest element in the array that is smaller than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The float value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found. + * @throws NullPointerException If array is null + */ + public static int floor(final float[] array, final float value) { + return floor(array, 0, array.length, value); + } + + /** + * Searches the specified array for the floor of the specified float value using binary search + * algorithm within the specified range. + * The floor of an float value within a range is the greatest element within the range that is smaller + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The float value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int floor(final float[] array, + final int fromIndex, + final int toIndex, + final float value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (array[fromIndex] > value) { + return INDEX_NOT_FOUND; + } + if (array[toIndex - 1] <= value) { + return toIndex - 1; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] <= value) { + return mid - 1; + } + return floor(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] > value) { + return mid; + } + return floor(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the floor of the specified double value using binary search + * algorithm. + * The floor of an double value is the greatest element in the array that is smaller than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The double value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found. + * @throws NullPointerException If array is null + */ + public static int floor(final double[] array, final double value) { + return floor(array, 0, array.length, value); + } + + /** + * Searches the specified array for the floor of the specified double value using binary search + * algorithm within the specified range. + * The floor of an double value within a range is the greatest element within the range that is smaller + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The double value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int floor(final double[] array, + final int fromIndex, + final int toIndex, + final double value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (array[fromIndex] > value) { + return INDEX_NOT_FOUND; + } + if (array[toIndex - 1] <= value) { + return toIndex - 1; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] <= value) { + return mid - 1; + } + return floor(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] > value) { + return mid; + } + return floor(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the floor of the specified short value using binary search + * algorithm. + * The floor of an short value is the greatest element in the array that is smaller than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The short value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found. + * @throws NullPointerException If array is null + */ + public static int floor(final short[] array, final short value) { + return floor(array, 0, array.length, value); + } + + /** + * Searches the specified array for the floor of the specified short value using binary search + * algorithm within the specified range. + * The floor of an short value within a range is the greatest element within the range that is smaller + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The short value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int floor(final short[] array, + final int fromIndex, + final int toIndex, + final short value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (array[fromIndex] > value) { + return INDEX_NOT_FOUND; + } + if (array[toIndex - 1] <= value) { + return toIndex - 1; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] <= value) { + return mid - 1; + } + return floor(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] > value) { + return mid; + } + return floor(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the floor of the specified byte value using binary search + * algorithm. + * The floor of an byte value is the greatest element in the array that is smaller than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The byte value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found. + * @throws NullPointerException If array is null + */ + public static int floor(final byte[] array, final byte value) { + return floor(array, 0, array.length, value); + } + + /** + * Searches the specified array for the floor of the specified byte value using binary search + * algorithm within the specified range. + * The floor of an byte value within a range is the greatest element within the range that is smaller + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The byte value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int floor(final byte[] array, + final int fromIndex, + final int toIndex, + final byte value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (array[fromIndex] > value) { + return INDEX_NOT_FOUND; + } + if (array[toIndex - 1] <= value) { + return toIndex - 1; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] <= value) { + return mid - 1; + } + return floor(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] > value) { + return mid; + } + return floor(array, mid + 1, toIndex, value); + } + + /** + * Searches the specified array for the floor of the specified char value using binary search + * algorithm. + * The floor of an char value is the greatest element in the array that is smaller than or equal to + * the value. + * The input array must be sorted in ascending order. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * + * @param array The sorted array to be searched + * @param value The char value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found. + * @throws NullPointerException If array is null + */ + public static int floor(final char[] array, final char value) { + return floor(array, 0, array.length, value); + } + + /** + * Searches the specified array for the floor of the specified char value using binary search + * algorithm within the specified range. + * The floor of an char value within a range is the greatest element within the range that is smaller + * than or equal to the value. + * The input array must be sorted in ascending order within the specified range. + * The behavior of the method when passing an unsorted array (or an array that is sorted in descending order) + * is undefined. + * The range is exclusive from the right end. + * + * @param array The sorted array to be searched + * @param fromIndex The index of the first element (inclusive) to be searched + * @param toIndex The index of the last element (exclusive) to be searched + * @param value The char value for which we want to find a floor + * @return The index of the floor if it is found. -1 will be returned if no floor was found within the range. + * @throws NullPointerException If array is null + * @throws IllegalArgumentException If any of the indices are out of bound + */ + public static int floor(final char[] array, + final int fromIndex, + final int toIndex, + final char value) { + Validate.notNull(array); + Validate.inclusiveBetween(0, array.length - 1, fromIndex); + Validate.inclusiveBetween(0, array.length, toIndex); + if (fromIndex >= toIndex) { + return INDEX_NOT_FOUND; + } + if (array[fromIndex] > value) { + return INDEX_NOT_FOUND; + } + if (array[toIndex - 1] <= value) { + return toIndex - 1; + } + final int mid = (fromIndex + toIndex - 1) >>> 1; + if (array[mid] == value) { + return mid; + } else if (array[mid] > value) { + if (fromIndex <= mid - 1 && array[mid - 1] <= value) { + return mid - 1; + } + return floor(array, fromIndex, mid, value); + } + if (mid + 1 <= toIndex - 1 && array[mid + 1] > value) { + return mid; + } + return floor(array, mid + 1, toIndex, value); + } + /** * Returns a copy of the given array of size 1 greater than the argument. * The last value of the array is left to the default value. diff --git a/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java b/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java index 292a4a5fd73..b856919fc26 100644 --- a/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java @@ -357,6 +357,112 @@ public void testContainsShort() { assertFalse(ArrayUtils.contains(array, (short) 99)); } + @Test + void testCeilingNullArray() { + boolean succeed = false; + try { + ArrayUtils.ceiling(null, 0, 0, 1, Integer::compareTo); + } catch (Exception ignored) { + succeed = true; + } + assertTrue(succeed); + } + + @Test + void testCeilingBoxedNumbers() { + final Integer[] integers = {12, 14, 40, 70, 90}; + // full array + assertEquals(0, ArrayUtils.ceiling(integers, 12)); + assertEquals(1, ArrayUtils.ceiling(integers, 13)); + assertEquals(2, ArrayUtils.ceiling(integers, 40)); + assertEquals(3, ArrayUtils.ceiling(integers, 50)); + assertEquals(4, ArrayUtils.ceiling(integers, 89)); + assertEquals(-1, ArrayUtils.ceiling(integers, 91)); + assertEquals(0, ArrayUtils.ceiling(integers, 0)); + // range + assertEquals(1, ArrayUtils.ceiling(integers, 1, 3, 12, Integer::compareTo)); + assertEquals(1, ArrayUtils.ceiling(integers, 1, 3, 13, Integer::compareTo)); + assertEquals(2, ArrayUtils.ceiling(integers, 1, 3, 40, Integer::compareTo)); + assertEquals(-1, ArrayUtils.ceiling(integers, 1, 3, 50, Integer::compareTo)); + assertEquals(-1, ArrayUtils.ceiling(integers, 1, 3, 89, Integer::compareTo)); + assertEquals(-1, ArrayUtils.ceiling(integers, 1, 3, 91, Integer::compareTo)); + assertEquals(1, ArrayUtils.ceiling(integers, 1, 3, 0, Integer::compareTo)); + } + + @Test + void testCeilingPrimitiveNumbers() { + final int[] integers = {12, 14, 40, 70, 90}; + // full array + assertEquals(0, ArrayUtils.ceiling(integers, 12)); + assertEquals(1, ArrayUtils.ceiling(integers, 13)); + assertEquals(2, ArrayUtils.ceiling(integers, 40)); + assertEquals(3, ArrayUtils.ceiling(integers, 50)); + assertEquals(4, ArrayUtils.ceiling(integers, 89)); + assertEquals(-1, ArrayUtils.ceiling(integers, 91)); + assertEquals(0, ArrayUtils.ceiling(integers, 0)); + // range + assertEquals(1, ArrayUtils.ceiling(integers, 1, 3, 12)); + assertEquals(1, ArrayUtils.ceiling(integers, 1, 3, 13)); + assertEquals(2, ArrayUtils.ceiling(integers, 1, 3, 40)); + assertEquals(-1, ArrayUtils.ceiling(integers, 1, 3, 50)); + assertEquals(-1, ArrayUtils.ceiling(integers, 1, 3, 89)); + assertEquals(-1, ArrayUtils.ceiling(integers, 1, 3, 91)); + assertEquals(1, ArrayUtils.ceiling(integers, 1, 3, 0)); + } + + @Test + void testFloorNullArray() { + boolean succeed = false; + try { + ArrayUtils.floor(null, 0, 0, 1, Integer::compareTo); + } catch (Exception ignored) { + succeed = true; + } + assertTrue(succeed); + } + + @Test + void testFloorBoxedNumbers() { + final Integer[] integers = {12, 14, 40, 70, 90}; + // full array + assertEquals(0, ArrayUtils.floor(integers, 12)); + assertEquals(0, ArrayUtils.floor(integers, 13)); + assertEquals(2, ArrayUtils.floor(integers, 40)); + assertEquals(2, ArrayUtils.floor(integers, 50)); + assertEquals(3, ArrayUtils.floor(integers, 89)); + assertEquals(4, ArrayUtils.floor(integers, 91)); + assertEquals(-1, ArrayUtils.floor(integers, 0)); + // range + assertEquals(-1, ArrayUtils.floor(integers, 1, 3, 12, Integer::compareTo)); + assertEquals(-1, ArrayUtils.floor(integers, 1, 3, 13, Integer::compareTo)); + assertEquals(2, ArrayUtils.floor(integers, 1, 3, 40, Integer::compareTo)); + assertEquals(2, ArrayUtils.floor(integers, 1, 3, 50, Integer::compareTo)); + assertEquals(2, ArrayUtils.floor(integers, 1, 3, 89, Integer::compareTo)); + assertEquals(2, ArrayUtils.floor(integers, 1, 3, 91, Integer::compareTo)); + assertEquals(-1, ArrayUtils.floor(integers, 1, 3, 0, Integer::compareTo)); + } + + @Test + void testFloorPrimitiveNumbers() { + final int[] integers = {12, 14, 40, 70, 90}; + // full array + assertEquals(0, ArrayUtils.floor(integers, 12)); + assertEquals(0, ArrayUtils.floor(integers, 13)); + assertEquals(2, ArrayUtils.floor(integers, 40)); + assertEquals(2, ArrayUtils.floor(integers, 50)); + assertEquals(3, ArrayUtils.floor(integers, 89)); + assertEquals(4, ArrayUtils.floor(integers, 91)); + assertEquals(-1, ArrayUtils.floor(integers, 0)); + // range + assertEquals(-1, ArrayUtils.floor(integers, 1, 3, 12)); + assertEquals(-1, ArrayUtils.floor(integers, 1, 3, 13)); + assertEquals(2, ArrayUtils.floor(integers, 1, 3, 40)); + assertEquals(2, ArrayUtils.floor(integers, 1, 3, 50)); + assertEquals(2, ArrayUtils.floor(integers, 1, 3, 89)); + assertEquals(2, ArrayUtils.floor(integers, 1, 3, 91)); + assertEquals(-1, ArrayUtils.floor(integers, 1, 3, 0)); + } + @Test public void testCreatePrimitiveArray() { assertNull(ArrayUtils.toPrimitive((Object[]) null));