From 32772ba3779e064cc3c3156806e8885c333f4640 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Tue, 3 Dec 2024 07:38:31 -0600 Subject: [PATCH] Search submodule which initially includes binary search --- .../function/ExceptionObjIntFunction.java | 16 + .../function/ExceptionObjIntPredicate.java | 24 + .../function/ExceptionObjLongFunction.java | 16 + .../function/ExceptionObjLongPredicate.java | 24 + .../common/function/ObjIntFunction.java | 16 + .../common/function/ObjIntPredicate.java | 24 + .../common/function/ObjLongFunction.java | 16 + .../common/function/ObjLongPredicate.java | 24 + pom.xml | 1 + search/pom.xml | 56 + .../smallrye/common/search/BinarySearch.java | 1149 +++++++++++++++++ search/src/main/java/module-info.java | 5 + .../smallrye/common/search/TestIntRange.java | 69 + .../smallrye/common/search/TestLongRange.java | 69 + 14 files changed, 1509 insertions(+) create mode 100644 function/src/main/java/io/smallrye/common/function/ExceptionObjIntFunction.java create mode 100644 function/src/main/java/io/smallrye/common/function/ExceptionObjIntPredicate.java create mode 100644 function/src/main/java/io/smallrye/common/function/ExceptionObjLongFunction.java create mode 100644 function/src/main/java/io/smallrye/common/function/ExceptionObjLongPredicate.java create mode 100644 function/src/main/java/io/smallrye/common/function/ObjIntFunction.java create mode 100644 function/src/main/java/io/smallrye/common/function/ObjIntPredicate.java create mode 100644 function/src/main/java/io/smallrye/common/function/ObjLongFunction.java create mode 100644 function/src/main/java/io/smallrye/common/function/ObjLongPredicate.java create mode 100644 search/pom.xml create mode 100644 search/src/main/java/io/smallrye/common/search/BinarySearch.java create mode 100644 search/src/main/java/module-info.java create mode 100644 search/src/test/java/io/smallrye/common/search/TestIntRange.java create mode 100644 search/src/test/java/io/smallrye/common/search/TestLongRange.java diff --git a/function/src/main/java/io/smallrye/common/function/ExceptionObjIntFunction.java b/function/src/main/java/io/smallrye/common/function/ExceptionObjIntFunction.java new file mode 100644 index 00000000..f2270d26 --- /dev/null +++ b/function/src/main/java/io/smallrye/common/function/ExceptionObjIntFunction.java @@ -0,0 +1,16 @@ +package io.smallrye.common.function; + +import java.util.Objects; + +/** + * A function which operates on an object and an integer, yielding an object. + */ +public interface ExceptionObjIntFunction { + R apply(T arg1, int arg2) throws E; + + default ExceptionObjIntFunction andThen(ExceptionFunction after) + throws E { + Objects.requireNonNull(after); + return (T t, int u) -> after.apply(apply(t, u)); + } +} diff --git a/function/src/main/java/io/smallrye/common/function/ExceptionObjIntPredicate.java b/function/src/main/java/io/smallrye/common/function/ExceptionObjIntPredicate.java new file mode 100644 index 00000000..70104e67 --- /dev/null +++ b/function/src/main/java/io/smallrye/common/function/ExceptionObjIntPredicate.java @@ -0,0 +1,24 @@ +package io.smallrye.common.function; + +import java.util.Objects; + +/** + * A predicate that operates on an object and an integer. + */ +public interface ExceptionObjIntPredicate { + boolean test(T object, int value) throws E; + + default ExceptionObjIntPredicate and(ExceptionObjIntPredicate other) { + Objects.requireNonNull(other); + return (t, i) -> test(t, i) && other.test(t, i); + } + + default ExceptionObjIntPredicate negate() { + return (t, i) -> !test(t, i); + } + + default ExceptionObjIntPredicate or(ExceptionObjIntPredicate other) { + Objects.requireNonNull(other); + return (t, i) -> test(t, i) || other.test(t, i); + } +} diff --git a/function/src/main/java/io/smallrye/common/function/ExceptionObjLongFunction.java b/function/src/main/java/io/smallrye/common/function/ExceptionObjLongFunction.java new file mode 100644 index 00000000..55e5ed61 --- /dev/null +++ b/function/src/main/java/io/smallrye/common/function/ExceptionObjLongFunction.java @@ -0,0 +1,16 @@ +package io.smallrye.common.function; + +import java.util.Objects; + +/** + * A function which operates on an object and a long integer, yielding an object. + */ +public interface ExceptionObjLongFunction { + R apply(T arg1, long arg2) throws E; + + default ExceptionObjLongFunction andThen(ExceptionFunction after) + throws E { + Objects.requireNonNull(after); + return (T t, long u) -> after.apply(apply(t, u)); + } +} diff --git a/function/src/main/java/io/smallrye/common/function/ExceptionObjLongPredicate.java b/function/src/main/java/io/smallrye/common/function/ExceptionObjLongPredicate.java new file mode 100644 index 00000000..0d0df0a9 --- /dev/null +++ b/function/src/main/java/io/smallrye/common/function/ExceptionObjLongPredicate.java @@ -0,0 +1,24 @@ +package io.smallrye.common.function; + +import java.util.Objects; + +/** + * A predicate that operates on an object and a long integer. + */ +public interface ExceptionObjLongPredicate { + boolean test(T object, long value); + + default ExceptionObjLongPredicate and(ExceptionObjLongPredicate other) { + Objects.requireNonNull(other); + return (t, i) -> test(t, i) && other.test(t, i); + } + + default ExceptionObjLongPredicate negate() { + return (t, i) -> !test(t, i); + } + + default ExceptionObjLongPredicate or(ExceptionObjLongPredicate other) { + Objects.requireNonNull(other); + return (t, i) -> test(t, i) || other.test(t, i); + } +} diff --git a/function/src/main/java/io/smallrye/common/function/ObjIntFunction.java b/function/src/main/java/io/smallrye/common/function/ObjIntFunction.java new file mode 100644 index 00000000..fcc5c6ae --- /dev/null +++ b/function/src/main/java/io/smallrye/common/function/ObjIntFunction.java @@ -0,0 +1,16 @@ +package io.smallrye.common.function; + +import java.util.Objects; +import java.util.function.Function; + +/** + * A function which operates on an object and an integer, yielding an object. + */ +public interface ObjIntFunction { + R apply(T arg1, int arg2); + + default ObjIntFunction andThen(Function after) { + Objects.requireNonNull(after); + return (T t, int u) -> after.apply(apply(t, u)); + } +} diff --git a/function/src/main/java/io/smallrye/common/function/ObjIntPredicate.java b/function/src/main/java/io/smallrye/common/function/ObjIntPredicate.java new file mode 100644 index 00000000..c7c33d55 --- /dev/null +++ b/function/src/main/java/io/smallrye/common/function/ObjIntPredicate.java @@ -0,0 +1,24 @@ +package io.smallrye.common.function; + +import java.util.Objects; + +/** + * A predicate that operates on an object and an integer. + */ +public interface ObjIntPredicate { + boolean test(T object, int value); + + default ObjIntPredicate and(ObjIntPredicate other) { + Objects.requireNonNull(other); + return (t, i) -> test(t, i) && other.test(t, i); + } + + default ObjIntPredicate negate() { + return (t, i) -> !test(t, i); + } + + default ObjIntPredicate or(ObjIntPredicate other) { + Objects.requireNonNull(other); + return (t, i) -> test(t, i) || other.test(t, i); + } +} diff --git a/function/src/main/java/io/smallrye/common/function/ObjLongFunction.java b/function/src/main/java/io/smallrye/common/function/ObjLongFunction.java new file mode 100644 index 00000000..7ce04cec --- /dev/null +++ b/function/src/main/java/io/smallrye/common/function/ObjLongFunction.java @@ -0,0 +1,16 @@ +package io.smallrye.common.function; + +import java.util.Objects; +import java.util.function.Function; + +/** + * A function which operates on an object and a long integer, yielding an object. + */ +public interface ObjLongFunction { + R apply(T arg1, long arg2); + + default ObjLongFunction andThen(Function after) { + Objects.requireNonNull(after); + return (T t, long u) -> after.apply(apply(t, u)); + } +} diff --git a/function/src/main/java/io/smallrye/common/function/ObjLongPredicate.java b/function/src/main/java/io/smallrye/common/function/ObjLongPredicate.java new file mode 100644 index 00000000..8365940c --- /dev/null +++ b/function/src/main/java/io/smallrye/common/function/ObjLongPredicate.java @@ -0,0 +1,24 @@ +package io.smallrye.common.function; + +import java.util.Objects; + +/** + * A predicate that operates on an object and a long integer. + */ +public interface ObjLongPredicate { + boolean test(T object, long value); + + default ObjLongPredicate and(ObjLongPredicate other) { + Objects.requireNonNull(other); + return (t, i) -> test(t, i) && other.test(t, i); + } + + default ObjLongPredicate negate() { + return (t, i) -> !test(t, i); + } + + default ObjLongPredicate or(ObjLongPredicate other) { + Objects.requireNonNull(other); + return (t, i) -> test(t, i) || other.test(t, i); + } +} diff --git a/pom.xml b/pom.xml index 16a5e650..5ecde9b6 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,7 @@ os ref resource + search version vertx-context diff --git a/search/pom.xml b/search/pom.xml new file mode 100644 index 00000000..7814a52c --- /dev/null +++ b/search/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + + io.smallrye.common + smallrye-common-parent + 3.0.0-SNAPSHOT + + + smallrye-common-search + + SmallRye Common: Search + + + + ${project.groupId} + smallrye-common-function + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + coverage + + @{jacocoArgLine} + + + + + org.jacoco + jacoco-maven-plugin + + + report + verify + + report + + + + + + + + + + + \ No newline at end of file diff --git a/search/src/main/java/io/smallrye/common/search/BinarySearch.java b/search/src/main/java/io/smallrye/common/search/BinarySearch.java new file mode 100644 index 00000000..e063fb5b --- /dev/null +++ b/search/src/main/java/io/smallrye/common/search/BinarySearch.java @@ -0,0 +1,1149 @@ +package io.smallrye.common.search; + +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.BinaryOperator; +import java.util.function.IntBinaryOperator; +import java.util.function.IntPredicate; +import java.util.function.LongBinaryOperator; +import java.util.function.LongPredicate; +import java.util.function.Predicate; + +import io.smallrye.common.function.ObjIntFunction; +import io.smallrye.common.function.ObjIntPredicate; +import io.smallrye.common.function.ObjLongFunction; +import io.smallrye.common.function.ObjLongPredicate; + +/** + * A utility class which provides multiple variations on binary searching. + * + *

Ranges

+ * + * The binary search algorithm operates over a range of values. + * The search will always return in logarithmic time with respect to the search interval. + * This implementation supports multiple kinds of value ranges: + *
    + *
  • {@code int} via the {@link #intRange()} method
  • + *
  • {@code long} via the {@link #longRange()} method
  • + *
  • Object ranges via the {@link #objRange()} method
  • + *
+ * + * Each of these methods returns an object with {@code find()} methods that perform variations on the binary search. + * + *

Range predicates

+ * + * Ranges are searched by predicate. + * The predicate is evaluated for the lowest value within the range + * (that is, the value closest to the {@code from} argument) for which it returns {@code true}. + * If no value satisfies the predicate, the highest value (that is, the value given for the {@code to} argument) is returned. + * The value returned by each {@code find()} method is always within the range {@code [from, to]}. + *

+ * In order to be well-defined, the predicate must be continuous, which is to say that + * given some value {@code n}, + * the predicate must return {@code false} for all {@code < n} and {@code true} for all {@code >= n} + * when {@code from ≤ n ≤ to}. + * If this constraint does not hold, then the results of the search will not be well-defined. + * The behavior of the predicate outside of this range does not affect the well-definedness of the search operation. + * + *

Inverted ranges

+ * + * It is possible to search over an inverted range, i.e. + * a range for which {@code to} is numerically lower than {@code from}. + * While such searches are well-defined, it should be noted that the {@code from} bound + * remains inclusive while the {@code to} bound remains exclusive, + * which might be surprising in some circumstances. + */ +public final class BinarySearch { + private BinarySearch() { + } + + /** + * {@return an object which can perform binary searches over an integer range (not null)} + * The returned object operates on a signed range by default. + * + * @see IntRange#unsigned() + */ + public static IntRange intRange() { + return IntRange.signed; + } + + /** + * {@return an object which can perform binary searches over a long integer range (not null)} + * The returned object operates on a signed range by default. + * + * @see LongRange#unsigned() + */ + public static LongRange longRange() { + return LongRange.signed; + } + + /** + * {@return an object which can perform binary searches over an object range (not null)} + */ + public static ObjRange objRange() { + return ObjRange.instance; + } + + /** + * A getter function which can satisfy {@link ObjIntFunction ObjIntFunction<E[], E>} for accessing + * values in an array. + * + * @param array the array (must not be {@code null}) + * @param index the array index + * @return the array value at the index + * @param the array element type + */ + public static E getFromArray(E[] array, int index) { + return array[index]; + } + + /** + * Search operations which apply over an {@code int} range. + */ + public static final class IntRange { + private final IntBinaryOperator midpoint; + private final InCollection inCollection; + + private IntRange(final IntBinaryOperator midpoint) { + this.midpoint = midpoint; + inCollection = new InCollection(midpoint); + } + + private static final IntRange signed = new IntRange(BinarySearch::signedMidpoint); + private static final IntRange unsigned = new IntRange(BinarySearch::unsignedMidpoint); + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the test + * within the range + */ + public int find(int from, int to, IntPredicate test) { + return searchInt(from, to, test, null, null, null, midpoint, IntRange::find0); + } + + private static boolean find0(int idx, IntPredicate test, Void ignored0, Void ignored1, Void ignored2) { + return test.test(idx); + } + + /** + * {@return search operations over a signed-integer space} + */ + public IntRange signed() { + return signed; + } + + /** + * {@return search operations over an unsigned-integer space} + */ + public IntRange unsigned() { + return unsigned; + } + + /** + * {@return search operations over a space defined by a custom midpoint function} + */ + public CustomMidpoint customMidpoint() { + return CustomMidpoint.instance; + } + + /** + * {@return search operations over a collection using the current signedness rules for the midpoint function} + */ + public InCollection inCollection() { + return inCollection; + } + + /** + * Search operations which apply over an {@code int} range using a custom midpoint function. + */ + public static final class CustomMidpoint { + private static final CustomMidpoint instance = new CustomMidpoint(); + + private CustomMidpoint() { + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the test + * within the range + */ + public int find(int from, int to, IntBinaryOperator midpoint, IntPredicate test) { + return searchInt(from, to, test, null, null, null, midpoint, CustomMidpoint::find0); + } + + private static boolean find0(int idx, IntPredicate test, Void ignored0, Void ignored1, Void ignored2) { + return test.test(idx); + } + + /** + * {@return search operations over a collection using a custom midpoint function} + */ + public InCollection inCollection() { + return InCollection.instance; + } + + /** + * Search operations within a collection using a custom midpoint function. + */ + public static final class InCollection { + private static final InCollection instance = new InCollection(); + + private InCollection() { + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param collection the collection object (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the + * test within the range + * @param the collection type + */ + public int find(C collection, int from, int to, IntBinaryOperator midpoint, ObjIntPredicate test) { + return searchInt(from, to, collection, test, (Void) null, (Void) null, midpoint, + IntRange.InCollection::find0); + } + + /** + * Get the lowest index within the given random-access list that satisfies the given test. + * + * @param list the list object (must not be {@code null}) + * @param midpoint the midpoint function (must not be {@code null}) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the + * test within the range + * @param the list type + */ + public > int find(L list, IntBinaryOperator midpoint, ObjIntPredicate test) { + return find(list, 0, list.size(), midpoint, test); + } + + /** + * {@return search operations which operate by an extracted key} + */ + public ByKey byKey() { + return ByKey.instance; + } + + /** + * Search operations within a collection which operate on an extracted key using a custom midpoint function. + */ + public static final class ByKey { + private static final ByKey instance = new ByKey(); + + private ByKey() { + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param collection the collection object (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param keyTester the test for the key (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy + * the test within the range + * @param the collection type + * @param the key type + */ + public int find(C collection, int from, int to, IntBinaryOperator midpoint, + ObjIntFunction keyExtractor, Predicate keyTester) { + return searchInt(from, to, collection, keyExtractor, keyTester, (Void) null, midpoint, + IntRange.InCollection.ByKey::find0); + } + + /** + * Get the lowest index within the given range that satisfies the given test for the search value. + * + * @param collection the collection object (must not be {@code null}) + * @param searchVal the value to search for + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param keyTester the test for the key (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy + * the test within the range + * @param the collection type + * @param the key type + * @param the search value type + */ + public int find(C collection, V searchVal, int from, int to, IntBinaryOperator midpoint, + ObjIntFunction keyExtractor, + BiPredicate keyTester) { + return searchInt(from, to, collection, searchVal, keyExtractor, keyTester, midpoint, + IntRange.InCollection.ByKey::find1); + } + + /** + * Get the lowest index within the given range whose key is equal to or greater than + * the search key, according to the {@linkplain Comparator#naturalOrder() natural order} of + * the search space. + * + * @param collection the collection object (must not be {@code null}) + * @param searchKey the key to search for (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @return the lowest index within the range which satisfies the condition, or {@code to} if no values + * satisfy the condition within the range + * @param the collection type + * @param the key type + */ + public > int findFirst(C collection, K searchKey, int from, int to, + IntBinaryOperator midpoint, ObjIntFunction keyExtractor) { + return findFirst(collection, searchKey, from, to, midpoint, keyExtractor, Comparator.naturalOrder()); + } + + /** + * Get the lowest index within the given range whose key is equal to or greater than + * the search key, according to the given comparator. + * + * @param collection the collection object (must not be {@code null}) + * @param searchKey the key to search for (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param cmp the comparator (must not be {@code null}) + * @return the lowest index within the range which satisfies the condition, or {@code to} if no values + * satisfy the condition within the range + * @param the collection type + * @param the key type + */ + public int findFirst(C collection, K searchKey, int from, int to, IntBinaryOperator midpoint, + ObjIntFunction keyExtractor, + Comparator cmp) { + return searchInt(from, to, collection, searchKey, keyExtractor, cmp, midpoint, + IntRange.InCollection.ByKey::find2); + } + } + } + } + + public static final class InCollection { + private final IntBinaryOperator midpoint; + private final ByKey byKey; + + private InCollection(final IntBinaryOperator midpoint) { + this.midpoint = midpoint; + byKey = new ByKey(midpoint); + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param collection the collection object (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the test + * within the range + * @param the collection type + */ + public int find(C collection, int from, int to, ObjIntPredicate test) { + return searchInt(from, to, collection, test, (Void) null, (Void) null, midpoint, InCollection::find0); + } + + /** + * Get the lowest index within the given random-access list that satisfies the given test. + * + * @param list the list object (must not be {@code null}) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the test + * within the range + * @param the list type + */ + public > int find(L list, ObjIntPredicate test) { + return find(list, 0, list.size(), test); + } + + private static boolean find0(int idx, C collection, ObjIntPredicate test, Void ignored0, Void ignored1) { + return test.test(collection, idx); + } + + /** + * {@return search operations which operate by an extracted key} + */ + public ByKey byKey() { + return byKey; + } + + /** + * Search operations within a collection which operate on an extracted key. + */ + public static final class ByKey { + private final IntBinaryOperator midpoint; + + private ByKey(IntBinaryOperator midpoint) { + this.midpoint = midpoint; + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param collection the collection object (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param keyTester the test for the key (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the + * test within the range + * @param the collection type + * @param the key type + */ + public int find(C collection, int from, int to, ObjIntFunction keyExtractor, + Predicate keyTester) { + return searchInt(from, to, collection, keyExtractor, keyTester, (Void) null, midpoint, ByKey::find0); + } + + private static boolean find0(int idx, C collection, ObjIntFunction keyExtractor, + Predicate keyTester, Void ignored) { + return keyTester.test(keyExtractor.apply(collection, idx)); + } + + /** + * Get the lowest index within the given range that satisfies the given test for the search value. + * + * @param collection the collection object (must not be {@code null}) + * @param searchVal the value to search for + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param keyTester the test for the key (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the + * test within the range + * @param the collection type + * @param the key type + * @param the value type + */ + public int find(C collection, V searchVal, int from, int to, ObjIntFunction keyExtractor, + BiPredicate keyTester) { + return searchInt(from, to, collection, searchVal, keyExtractor, keyTester, midpoint, ByKey::find1); + } + + private static boolean find1(int idx, C collection, V searchVal, ObjIntFunction keyExtractor, + BiPredicate keyTester) { + return keyTester.test(searchVal, keyExtractor.apply(collection, idx)); + } + + /** + * Get the lowest index within the given range whose key is equal to or greater than + * the search key, according to the {@linkplain Comparator#naturalOrder() natural order} of + * the search space. + * + * @param collection the collection object (must not be {@code null}) + * @param searchKey the key to search for (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @return the lowest index within the range which satisfies the condition, or {@code to} if no values satisfy + * the condition within the range + * @param the collection type + * @param the key type + */ + public > int findFirst(C collection, K searchKey, int from, int to, + ObjIntFunction keyExtractor) { + return findFirst(collection, searchKey, from, to, keyExtractor, Comparator.naturalOrder()); + } + + /** + * Get the lowest index within the given range whose key is equal to or greater than + * the search key, according to the given comparator. + * + * @param collection the collection object (must not be {@code null}) + * @param searchKey the key to search for (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param cmp the comparator (must not be {@code null}) + * @return the lowest index within the range which satisfies the condition, or {@code to} if no values satisfy + * the condition within the range + * @param the collection type + * @param the key type + */ + public int findFirst(C collection, K searchKey, int from, int to, ObjIntFunction keyExtractor, + Comparator cmp) { + return searchInt(from, to, collection, searchKey, keyExtractor, cmp, midpoint, ByKey::find2); + } + + private static boolean find2(int idx, C collection, K searchKey, ObjIntFunction keyExtractor, + Comparator cmp) { + return cmp.compare(searchKey, keyExtractor.apply(collection, idx)) >= 0; + } + } + } + } + + /** + * Search operations which apply over an {@code long} range. + */ + public static final class LongRange { + private final LongBinaryOperator midpoint; + private final InCollection inCollection; + + private LongRange(final LongBinaryOperator midpoint) { + this.midpoint = midpoint; + inCollection = new InCollection(midpoint); + } + + private static final LongRange signed = new LongRange(BinarySearch::signedMidpoint); + private static final LongRange unsigned = new LongRange(BinarySearch::unsignedMidpoint); + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the test + * within the range + */ + public long find(long from, long to, LongPredicate test) { + return searchLong(from, to, test, null, null, null, midpoint, LongRange::find0); + } + + private static boolean find0(long idx, LongPredicate test, Void ignored0, Void ignored1, Void ignored2) { + return test.test(idx); + } + + /** + * {@return search operations over a signed-integer space} + */ + public LongRange signed() { + return signed; + } + + /** + * {@return search operations over an unsigned-integer space} + */ + public LongRange unsigned() { + return unsigned; + } + + /** + * {@return search operations over a space defined by a custom midpoint function} + */ + public CustomMidpoint customMidpoint() { + return CustomMidpoint.instance; + } + + /** + * {@return search operations over a collection using the current signedness rules for the midpoint function} + */ + public InCollection inCollection() { + return inCollection; + } + + /** + * Search operations which apply over an {@code long} range using a custom midpoint function. + */ + public static final class CustomMidpoint { + private static final CustomMidpoint instance = new CustomMidpoint(); + + private CustomMidpoint() { + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the test + * within the range + */ + public long find(long from, long to, LongBinaryOperator midpoint, LongPredicate test) { + return searchLong(from, to, test, null, null, null, midpoint, CustomMidpoint::find0); + } + + private static boolean find0(long idx, LongPredicate test, Void ignored0, Void ignored1, Void ignored2) { + return test.test(idx); + } + + /** + * {@return search operations over a collection using a custom midpoint function} + */ + public InCollection inCollection() { + return InCollection.instance; + } + + /** + * Search operations within a collection using a custom midpoint function. + */ + public static final class InCollection { + private static final InCollection instance = new InCollection(); + + private InCollection() { + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param collection the collection object (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the + * test within the range + * @param the collection type + */ + public long find(C collection, long from, long to, LongBinaryOperator midpoint, ObjLongPredicate test) { + return searchLong(from, to, collection, test, (Void) null, (Void) null, midpoint, + LongRange.InCollection::find0); + } + + /** + * {@return search operations which operate by an extracted key} + */ + public ByKey byKey() { + return ByKey.instance; + } + + /** + * Search operations within a collection which operate on an extracted key using a custom midpoint function. + */ + public static final class ByKey { + private static final ByKey instance = new ByKey(); + + private ByKey() { + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param collection the collection object (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param keyTester the test for the key (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy + * the test within the range + * @param the collection type + * @param the key type + */ + public long find(C collection, long from, long to, LongBinaryOperator midpoint, + ObjLongFunction keyExtractor, Predicate keyTester) { + return searchLong(from, to, collection, keyExtractor, keyTester, (Void) null, midpoint, + LongRange.InCollection.ByKey::find0); + } + + /** + * Get the lowest index within the given range that satisfies the given test for the search value. + * + * @param collection the collection object (must not be {@code null}) + * @param searchVal the value to search for + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param keyTester the test for the key (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy + * the test within the range + * @param the collection type + * @param the key type + * @param the value type + */ + public long find(C collection, V searchVal, long from, long to, LongBinaryOperator midpoint, + ObjLongFunction keyExtractor, BiPredicate keyTester) { + return searchLong(from, to, collection, searchVal, keyExtractor, keyTester, midpoint, + LongRange.InCollection.ByKey::find1); + } + + /** + * Get the lowest index within the given range whose key is equal to or greater than + * the search key, according to the {@linkplain Comparator#naturalOrder() natural order} of + * the search space. + * + * @param collection the collection object (must not be {@code null}) + * @param searchKey the key to search for (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @return the lowest index within the range which satisfies the condition, or {@code to} if no values + * satisfy the condition within the range + * @param the collection type + * @param the key type + */ + public > long findFirst(C collection, K searchKey, long from, long to, + LongBinaryOperator midpoint, ObjLongFunction keyExtractor) { + return findFirst(collection, searchKey, from, to, midpoint, keyExtractor, Comparator.naturalOrder()); + } + + /** + * Get the lowest index within the given range whose key is equal to or greater than + * the search key, according to the given comparator. + * + * @param collection the collection object (must not be {@code null}) + * @param searchKey the key to search for (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param cmp the comparator (must not be {@code null}) + * @return the lowest index within the range which satisfies the condition, or {@code to} if no values + * satisfy the condition within the range + * @param the collection type + * @param the key type + */ + public long findFirst(C collection, K searchKey, long from, long to, LongBinaryOperator midpoint, + ObjLongFunction keyExtractor, Comparator cmp) { + return searchLong(from, to, collection, searchKey, keyExtractor, cmp, midpoint, + LongRange.InCollection.ByKey::find2); + } + } + } + } + + public static final class InCollection { + private final LongBinaryOperator midpoint; + private final ByKey byKey; + + private InCollection(final LongBinaryOperator midpoint) { + this.midpoint = midpoint; + byKey = new ByKey(midpoint); + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param collection the collection object (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the test + * within the range + * @param the collection type + */ + public long find(C collection, long from, long to, ObjLongPredicate test) { + return searchLong(from, to, collection, test, (Void) null, (Void) null, midpoint, InCollection::find0); + } + + private static boolean find0(long idx, C collection, ObjLongPredicate test, Void ignored0, Void ignored1) { + return test.test(collection, idx); + } + + /** + * {@return search operations which operate by an extracted key} + */ + public ByKey byKey() { + return byKey; + } + + /** + * Search operations within a collection which operate on an extracted key. + */ + public static final class ByKey { + private final LongBinaryOperator midpoint; + + private ByKey(LongBinaryOperator midpoint) { + this.midpoint = midpoint; + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param collection the collection object (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param keyTester the test for the key (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the + * test within the range + * @param the collection type + * @param the key type + */ + public long find(C collection, long from, long to, ObjLongFunction keyExtractor, + Predicate keyTester) { + return searchLong(from, to, collection, keyExtractor, keyTester, (Void) null, midpoint, ByKey::find0); + } + + private static boolean find0(long idx, C collection, ObjLongFunction keyExtractor, + Predicate keyTester, Void ignored) { + return keyTester.test(keyExtractor.apply(collection, idx)); + } + + /** + * Get the lowest index within the given range that satisfies the given test for the search value. + * + * @param collection the collection object (must not be {@code null}) + * @param searchVal the value to search for + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param keyTester the test for the key (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the + * test within the range + * @param the collection type + * @param the key type + * @param the value type + */ + public long find(C collection, V searchVal, long from, long to, ObjLongFunction keyExtractor, + BiPredicate keyTester) { + return searchLong(from, to, collection, searchVal, keyExtractor, keyTester, midpoint, ByKey::find1); + } + + private static boolean find1(long idx, C collection, V searchVal, ObjLongFunction keyExtractor, + BiPredicate keyTester) { + return keyTester.test(searchVal, keyExtractor.apply(collection, idx)); + } + + /** + * Get the lowest index within the given range whose key is equal to or greater than + * the search key, according to the {@linkplain Comparator#naturalOrder() natural order} of + * the search space. + * + * @param collection the collection object (must not be {@code null}) + * @param searchKey the key to search for (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @return the lowest index within the range which satisfies the condition, or {@code to} if no values satisfy + * the condition within the range + * @param the collection type + * @param the key type + */ + public > long findFirst(C collection, K searchKey, long from, long to, + ObjLongFunction keyExtractor) { + return findFirst(collection, searchKey, from, to, keyExtractor, Comparator.naturalOrder()); + } + + /** + * Get the lowest index within the given range whose key is equal to or greater than + * the search key, according to the given comparator. + * + * @param collection the collection object (must not be {@code null}) + * @param searchKey the key to search for (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param cmp the comparator (must not be {@code null}) + * @return the lowest index within the range which satisfies the condition, or {@code to} if no values satisfy + * the condition within the range + * @param the collection type + * @param the key type + */ + public long findFirst(C collection, K searchKey, long from, long to, ObjLongFunction keyExtractor, + Comparator cmp) { + return searchLong(from, to, collection, searchKey, keyExtractor, cmp, midpoint, ByKey::find2); + } + + private static boolean find2(long idx, C collection, K searchKey, ObjLongFunction keyExtractor, + Comparator cmp) { + return cmp.compare(searchKey, keyExtractor.apply(collection, idx)) >= 0; + } + } + } + } + + /** + * Binary searches over a range of objects. + */ + public static final class ObjRange { + private static final ObjRange instance = new ObjRange(); + + private ObjRange() { + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the test + * within the range + * @param the index type + */ + public T find(T from, T to, BinaryOperator midpoint, Predicate test) { + return searchObject(from, to, test, (Void) null, (Void) null, (Void) null, midpoint, ObjRange::find0); + } + + private static boolean find0(T idx, Predicate test, Void ignored0, Void ignored1, Void ignored2) { + return test.test(idx); + } + + /** + * {@return search operations over a collection} + */ + public static InCollection inCollection() { + return InCollection.instance; + } + + /** + * Search operations within a collection. + */ + public static final class InCollection { + private static final InCollection instance = new InCollection(); + + private InCollection() { + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param collection the collection object (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param test the test (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the test + * within the range + * @param the collection type + * @param the index type + */ + public T find(C collection, T from, T to, BinaryOperator midpoint, BiPredicate test) { + return searchObject(from, to, collection, test, (Void) null, (Void) null, midpoint, InCollection::find0); + } + + private static boolean find0(T idx, C collection, BiPredicate test, Void ignored0, Void ignored1) { + return test.test(collection, idx); + } + + /** + * {@return search operations which operate by an extracted key} + */ + public ByKey byKey() { + return ByKey.instance; + } + + /** + * Search operations within a collection which operate on an extracted key using a custom midpoint function. + */ + public static final class ByKey { + private static final ByKey instance = new ByKey(); + + private ByKey() { + } + + /** + * Get the lowest index within the given range that satisfies the given test. + * + * @param collection the collection object (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param test the test for the key (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the + * test within the range + * @param the index type + * @param the collection type + * @param the key type + */ + public T find(C collection, T from, T to, BinaryOperator midpoint, + BiFunction keyExtractor, + Predicate test) { + return searchObject(from, to, collection, keyExtractor, test, (Void) null, midpoint, ByKey::find0); + } + + private static boolean find0(T idx, C collection, BiFunction keyExtractor, Predicate test, + Void ignored) { + return test.test(keyExtractor.apply(collection, idx)); + } + + /** + * Get the lowest index within the given range that satisfies the given test for the search value. + * + * @param collection the collection object (must not be {@code null}) + * @param searchVal the value to search for + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param keyTester the test for the key (must not be {@code null}) + * @return the lowest index within the range which satisfies the test, or {@code to} if no values satisfy the + * test within the range + * @param the index type + * @param the collection type + * @param the key type + * @param the value type + */ + public T find(C collection, V searchVal, T from, T to, BinaryOperator midpoint, + BiFunction keyExtractor, + BiPredicate keyTester) { + return searchObject(from, to, collection, searchVal, keyExtractor, keyTester, midpoint, ByKey::find1); + } + + private static boolean find1(T idx, C collection, V searchVal, BiFunction keyExtractor, + BiPredicate test) { + return test.test(searchVal, keyExtractor.apply(collection, idx)); + } + + /** + * Get the lowest index within the given range whose key is equal to or greater than + * the search key, according to the {@linkplain Comparator#naturalOrder() natural order} of + * the search space. + * + * @param collection the collection object (must not be {@code null}) + * @param searchKey the key to search for (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @return the lowest index within the range which satisfies the condition, or {@code to} if no values satisfy + * the condition within the range + * @param the index type + * @param the collection type + * @param the key type + */ + public > T findFirst(C collection, K searchKey, T from, T to, + BinaryOperator midpoint, BiFunction keyExtractor) { + return findFirst(collection, searchKey, from, to, midpoint, keyExtractor, Comparator.naturalOrder()); + } + + /** + * Get the lowest index within the given range whose key is equal to or greater than + * the search key, according to the given comparator. + * + * @param collection the collection object (must not be {@code null}) + * @param searchKey the key to search for (must not be {@code null}) + * @param from the low end of the range (inclusive) + * @param to the high end of the range (exclusive) + * @param midpoint the midpoint function (must not be {@code null}) + * @param keyExtractor the key extraction function (must not be {@code null}) + * @param cmp the comparator (must not be {@code null}) + * @return the lowest index within the range which satisfies the condition, or {@code to} if no values satisfy + * the condition within the range + * @param the index type + * @param the collection type + * @param the key type + */ + public T findFirst(C collection, K searchKey, T from, T to, BinaryOperator midpoint, + BiFunction keyExtractor, + Comparator cmp) { + return searchObject(from, to, collection, searchKey, keyExtractor, cmp, midpoint, ByKey::find2); + } + + private static boolean find2(T idx, C collection, K searchKey, BiFunction keyExtractor, + Comparator cmp) { + return cmp.compare(searchKey, keyExtractor.apply(collection, idx)) >= 0; + } + } + } + } + + // midpoint functions + + private static int signedMidpoint(int from, int to) { + return from + (to - from >> 1); + } + + private static int unsignedMidpoint(int from, int to) { + return from + (to - from >>> 1); + } + + private static long signedMidpoint(long from, long to) { + return from + (to - from >> 1); + } + + private static long unsignedMidpoint(long from, long to) { + return from + (to - from >>> 1); + } + + // Theoretically, we could use only the object variation and rely on box types, compiler magic and + // future value type support for performance. However, this is not yet a reality so we will have + // three versions for now: one that operates on object ranges, and two that operate on integer ranges (int/long). + + private interface ObjIdxTestFunction { + boolean test(T idx, A a, B b, C c, D d); + } + + private static T searchObject(T from, T to, A a, B b, C c, D d, BinaryOperator midpoint, + ObjIdxTestFunction test) { + T mid = midpoint.apply(from, to); + T newMid; + for (;;) { + if (test.test(mid, a, b, c, d)) { + to = mid; + newMid = midpoint.apply(from, to); + if (Objects.equals(mid, newMid)) { + return from; + } + } else { + from = mid; + newMid = midpoint.apply(from, to); + if (Objects.equals(mid, newMid)) { + return to; + } + } + mid = newMid; + } + } + + private interface LongIdxTestFunction { + boolean test(long idx, A a, B b, C c, D d); + } + + private static long searchLong(long from, long to, A a, B b, C c, D d, LongBinaryOperator midpoint, + LongIdxTestFunction test) { + long mid = midpoint.applyAsLong(from, to); + long newMid; + for (;;) { + if (test.test(mid, a, b, c, d)) { + to = mid; + newMid = midpoint.applyAsLong(from, to); + if (mid == newMid) { + return from; + } + } else { + from = mid; + newMid = midpoint.applyAsLong(from, to); + if (mid == newMid) { + return to; + } + } + mid = newMid; + } + } + + private interface IntIdxTestFunction { + boolean test(int idx, A a, B b, C c, D d); + } + + private static int searchInt(int from, int to, A a, B b, C c, D d, IntBinaryOperator midpoint, + IntIdxTestFunction test) { + int mid = midpoint.applyAsInt(from, to); + int newMid; + for (;;) { + if (test.test(mid, a, b, c, d)) { + to = mid; + newMid = midpoint.applyAsInt(from, to); + if (mid == newMid) { + return from; + } + } else { + from = mid; + newMid = midpoint.applyAsInt(from, to); + if (mid == newMid) { + return to; + } + } + mid = newMid; + } + } +} diff --git a/search/src/main/java/module-info.java b/search/src/main/java/module-info.java new file mode 100644 index 00000000..fba9dd75 --- /dev/null +++ b/search/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module io.smallrye.common.search { + requires io.smallrye.common.function; + + exports io.smallrye.common.search; +} diff --git a/search/src/test/java/io/smallrye/common/search/TestIntRange.java b/search/src/test/java/io/smallrye/common/search/TestIntRange.java new file mode 100644 index 00000000..f49a0840 --- /dev/null +++ b/search/src/test/java/io/smallrye/common/search/TestIntRange.java @@ -0,0 +1,69 @@ +package io.smallrye.common.search; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public final class TestIntRange { + public TestIntRange() { + } + + private static final int MAX_INT_UNSIGNED = 0xffffffff; // == -1 + + @Test + public void testEmptyRange() { + assertEquals(1, BinarySearch.intRange().find(1, 1, i -> true)); + assertEquals(10, BinarySearch.intRange().find(10, 10, i -> true)); + } + + @Test + public void testRange() { + assertEquals(0, BinarySearch.intRange().find(0, 10, i -> i >= -100)); + assertEquals(0, BinarySearch.intRange().find(0, 10, i -> i >= 0)); + assertEquals(1, BinarySearch.intRange().find(0, 10, i -> i > 0)); + assertEquals(4, BinarySearch.intRange().find(0, 10, i -> i >= 4)); + assertEquals(5, BinarySearch.intRange().find(0, 10, i -> i > 4)); + assertEquals(5, BinarySearch.intRange().find(0, 10, i -> i >= 5)); + assertEquals(6, BinarySearch.intRange().find(0, 10, i -> i > 5)); + assertEquals(10, BinarySearch.intRange().find(0, 10, i -> i >= 10)); + assertEquals(10, BinarySearch.intRange().find(0, 10, i -> false)); + assertEquals(0, BinarySearch.intRange().find(0, 10, i -> true)); + // signed-specific ranges + assertEquals(-10, BinarySearch.intRange().find(-10, 10, i -> true)); + } + + @Test + public void testRangeUnsigned() { + assertEquals(0, BinarySearch.intRange().unsigned().find(0, 10, i -> i >= 0)); + assertEquals(1, BinarySearch.intRange().unsigned().find(0, 10, i -> i > 0)); + assertEquals(4, BinarySearch.intRange().unsigned().find(0, 10, i -> i >= 4)); + assertEquals(5, BinarySearch.intRange().unsigned().find(0, 10, i -> i > 4)); + assertEquals(5, BinarySearch.intRange().unsigned().find(0, 10, i -> i >= 5)); + assertEquals(6, BinarySearch.intRange().unsigned().find(0, 10, i -> i > 5)); + assertEquals(10, BinarySearch.intRange().unsigned().find(0, 10, i -> i >= 10)); + assertEquals(10, BinarySearch.intRange().unsigned().find(0, 10, i -> false)); + assertEquals(0, BinarySearch.intRange().unsigned().find(0, 10, i -> true)); + // unsigned-specific ranges + assertEquals(MAX_INT_UNSIGNED, BinarySearch.intRange().find(0, MAX_INT_UNSIGNED, i -> false)); + assertEquals(0, BinarySearch.intRange().unsigned().find(0, MAX_INT_UNSIGNED, i -> true)); + assertEquals(0x8000_0000, BinarySearch.intRange().unsigned().find(0, MAX_INT_UNSIGNED, + i -> Integer.compareUnsigned(i, 0x8000_0000) >= 0)); + } + + @Test + public void testBigRange() { + assertEquals(1025, BinarySearch.intRange().find(0, Integer.MAX_VALUE, i -> i >= 1025)); + assertEquals(1026, BinarySearch.intRange().find(0, Integer.MAX_VALUE, i -> i >= 1026)); + assertEquals(99210, BinarySearch.intRange().find(49203, 848392, i -> i >= 99210)); + assertEquals(Integer.MAX_VALUE, BinarySearch.intRange().find(0, Integer.MAX_VALUE, i -> false)); + assertEquals(0, BinarySearch.intRange().find(0, Integer.MAX_VALUE, i -> true)); + } + + @Test + public void testRevRange() { + assertEquals(5, BinarySearch.intRange().find(9, -1, i -> i < 5)); + assertEquals(6, BinarySearch.intRange().find(9, -1, i -> i <= 5)); + assertEquals(0, BinarySearch.intRange().find(9, -1, i -> i < 0)); + assertEquals(1, BinarySearch.intRange().find(9, -1, i -> i <= 0)); + } +} diff --git a/search/src/test/java/io/smallrye/common/search/TestLongRange.java b/search/src/test/java/io/smallrye/common/search/TestLongRange.java new file mode 100644 index 00000000..d0ebf7c4 --- /dev/null +++ b/search/src/test/java/io/smallrye/common/search/TestLongRange.java @@ -0,0 +1,69 @@ +package io.smallrye.common.search; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public final class TestLongRange { + public TestLongRange() { + } + + private static final long MAX_LONG_UNSIGNED = 0xffffffff_ffffffffL; // == -1L + + @Test + public void testEmptyRange() { + assertEquals(1, BinarySearch.longRange().find(1, 1, i -> true)); + assertEquals(10, BinarySearch.longRange().find(10, 10, i -> true)); + } + + @Test + public void testRange() { + assertEquals(0, BinarySearch.longRange().find(0, 10, i -> i >= -100)); + assertEquals(0, BinarySearch.longRange().find(0, 10, i -> i >= 0)); + assertEquals(1, BinarySearch.longRange().find(0, 10, i -> i > 0)); + assertEquals(4, BinarySearch.longRange().find(0, 10, i -> i >= 4)); + assertEquals(5, BinarySearch.longRange().find(0, 10, i -> i > 4)); + assertEquals(5, BinarySearch.longRange().find(0, 10, i -> i >= 5)); + assertEquals(6, BinarySearch.longRange().find(0, 10, i -> i > 5)); + assertEquals(10, BinarySearch.longRange().find(0, 10, i -> i >= 10)); + assertEquals(10, BinarySearch.longRange().find(0, 10, i -> false)); + assertEquals(0, BinarySearch.longRange().find(0, 10, i -> true)); + // signed-specific ranges + assertEquals(-10, BinarySearch.longRange().find(-10, 10, i -> true)); + } + + @Test + public void testRangeUnsigned() { + assertEquals(0, BinarySearch.longRange().unsigned().find(0, 10, i -> i >= 0)); + assertEquals(1, BinarySearch.longRange().unsigned().find(0, 10, i -> i > 0)); + assertEquals(4, BinarySearch.longRange().unsigned().find(0, 10, i -> i >= 4)); + assertEquals(5, BinarySearch.longRange().unsigned().find(0, 10, i -> i > 4)); + assertEquals(5, BinarySearch.longRange().unsigned().find(0, 10, i -> i >= 5)); + assertEquals(6, BinarySearch.longRange().unsigned().find(0, 10, i -> i > 5)); + assertEquals(10, BinarySearch.longRange().unsigned().find(0, 10, i -> i >= 10)); + assertEquals(10, BinarySearch.longRange().unsigned().find(0, 10, i -> false)); + assertEquals(0, BinarySearch.longRange().unsigned().find(0, 10, i -> true)); + // unsigned-specific ranges + assertEquals(MAX_LONG_UNSIGNED, BinarySearch.longRange().find(0, MAX_LONG_UNSIGNED, i -> false)); + assertEquals(0, BinarySearch.longRange().unsigned().find(0, MAX_LONG_UNSIGNED, i -> true)); + assertEquals(0x8000_0000_0000_0000L, BinarySearch.longRange().unsigned().find(0, MAX_LONG_UNSIGNED, + i -> Long.compareUnsigned(i, 0x8000_0000_0000_0000L) >= 0)); + } + + @Test + public void testBigRange() { + assertEquals(1025, BinarySearch.longRange().find(0, Long.MAX_VALUE, i -> i >= 1025)); + assertEquals(1026, BinarySearch.longRange().find(0, Long.MAX_VALUE, i -> i >= 1026)); + assertEquals(99210, BinarySearch.longRange().find(49203, 848392, i -> i >= 99210)); + assertEquals(Long.MAX_VALUE, BinarySearch.longRange().find(0, Long.MAX_VALUE, i -> false)); + assertEquals(0, BinarySearch.longRange().find(0, Long.MAX_VALUE, i -> true)); + } + + @Test + public void testRevRange() { + assertEquals(5, BinarySearch.longRange().find(9, -1, i -> i < 5)); + assertEquals(6, BinarySearch.longRange().find(9, -1, i -> i <= 5)); + assertEquals(0, BinarySearch.longRange().find(9, -1, i -> i < 0)); + assertEquals(1, BinarySearch.longRange().find(9, -1, i -> i <= 0)); + } +}