Have Extrema support non-Comparable types, so that it can potentially be used in more places in the codebase. Also clean up javadoc and document the performance characteristics. RELNOTES: None PiperOrigin-RevId: 255964454
diff --git a/src/main/java/com/google/devtools/build/lib/collect/Extrema.java b/src/main/java/com/google/devtools/build/lib/collect/Extrema.java index 6f0b9cf..e706a94 100644 --- a/src/main/java/com/google/devtools/build/lib/collect/Extrema.java +++ b/src/main/java/com/google/devtools/build/lib/collect/Extrema.java
@@ -18,26 +18,45 @@ import java.util.PriorityQueue; /** - * A stream aggregator that, given a {@code k}, aggregates a sequence of elements into the {@code k} - * most extreme. + * A streaming aggregator that, given a {@code k}, allows a streaming aggregation of {@code n} + * elements into the {@code min(k, n)} most extreme, in {@code O(min(k, n))} memory and {@code O(n * + * log(min(k, n)))} time. */ -public class Extrema<T extends Comparable<T>> { +public class Extrema<T> { private final int k; private final Comparator<T> extremaComparator; private final PriorityQueue<T> priorityQueue; /** - * Creates an {@link Extrema} that aggregates a sequence of elements into the {@code k} smallest. + * Creates an {@link Extrema} that can aggregate {@code n} elements into the {@code min(k, n)} + * smallest. */ public static <T extends Comparable<T>> Extrema<T> min(int k) { - return new Extrema<>(k, Comparator.<T>naturalOrder()); + return min(k, Comparator.<T>naturalOrder()); } /** - * Creates an {@link Extrema} that aggregates a sequence of elements into the {@code k} largest. + * Creates an {@link Extrema} that can aggregate {@code n} elements into the {@code min(k, n)} + * smallest, per the given {@code comparator}. + */ + public static <T> Extrema<T> min(int k, Comparator<T> comparator) { + return new Extrema<>(k, comparator); + } + + /** + * Creates an {@link Extrema} that can aggregate {@code n} elements into the {@code min(k, n)} + * largest. */ public static <T extends Comparable<T>> Extrema<T> max(int k) { - return new Extrema<>(k, Comparator.<T>naturalOrder().reversed()); + return max(k, Comparator.<T>naturalOrder()); + } + + /** + * Creates an {@link Extrema} that can aggregate {@code n} elements into the {@code min(k, n)} + * largest, per the given {@code comparator}. + */ + public static <T> Extrema<T> max(int k, Comparator<T> comparator) { + return new Extrema<>(k, comparator.reversed()); } /** @@ -77,8 +96,8 @@ /** * For an {@link Extrema} created with {@code k} and with {@code n} calls to {@link #aggregate} - * since the most recent call to {@link #clear}, returns the min(k, n) most extreme elements - * {@link #aggregate}'ed since the most recent call to {@link #clear}. + * since the most recent call to {@link #clear}, returns the {@code min(k, n)} most extreme of the + * those elements. */ public ImmutableList<T> getExtremeElements() { return ImmutableList.sortedCopyOf(extremaComparator, priorityQueue);
diff --git a/src/test/java/com/google/devtools/build/lib/collect/ExtremaTest.java b/src/test/java/com/google/devtools/build/lib/collect/ExtremaTest.java index 1f86a04..6a081e8 100644 --- a/src/test/java/com/google/devtools/build/lib/collect/ExtremaTest.java +++ b/src/test/java/com/google/devtools/build/lib/collect/ExtremaTest.java
@@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -42,6 +43,36 @@ } @Test + public void customComparator() { + class BoxedInt { + private final int i; + + private BoxedInt(int i) { + this.i = i; + } + } + Extrema<BoxedInt> extrema = + Extrema.max( + 2, + new Comparator<BoxedInt>() { + @Override + public int compare(BoxedInt bi1, BoxedInt bi2) { + return Integer.compare(bi1.i, bi2.i); + } + }); + extrema.aggregate(new BoxedInt(4)); + extrema.aggregate(new BoxedInt(1)); + extrema.aggregate(new BoxedInt(2)); + extrema.aggregate(new BoxedInt(3)); + extrema.aggregate(new BoxedInt(5)); + ImmutableList<Integer> extremeElements = + extrema.getExtremeElements().stream() + .map(bi -> bi.i) + .collect(ImmutableList.toImmutableList()); + assertThat(extremeElements).containsExactly(5, 4).inOrder(); + } + + @Test public void minExtremaSmallK() { runRangeTest(Extrema.min(5), 1, 100, ImmutableList.of(1, 2, 3, 4, 5)); }