HoustonPutman commented on code in PR #13914:
URL: https://github.com/apache/lucene/pull/13914#discussion_r1810967900


##########
lucene/facet/src/java/org/apache/lucene/facet/range/DynamicRangeUtil.java:
##########
@@ -202,66 +208,83 @@ public SegmentOutput(int hitsLength) {
    *     is used to compute the equi-weight per bin.
    */
   public static List<DynamicRangeInfo> computeDynamicNumericRanges(
-      long[] values, long[] weights, int len, long totalWeight, int topN) {
+      long[] values, long[] weights, int len, long totalValue, long 
totalWeight, int topN) {
     assert values.length == weights.length && len <= values.length && len >= 0;
     assert topN >= 0;
     List<DynamicRangeInfo> dynamicRangeResult = new ArrayList<>();
     if (len == 0 || topN == 0) {
       return dynamicRangeResult;
     }
 
-    new InPlaceMergeSorter() {
-      @Override
-      protected int compare(int index1, int index2) {
-        int cmp = Long.compare(values[index1], values[index2]);
-        if (cmp == 0) {
-          // If the values are equal, sort based on the weights.
-          // Any weight order is correct as long as it's deterministic.
-          return Long.compare(weights[index1], weights[index2]);
-        }
-        return cmp;
-      }
+    double rangeWeightTarget = (double) totalWeight / topN;
+    double[] kWeights = new double[topN];
+    for (int i = 0; i < topN; i++) {
+      kWeights[i] = (i == 0 ? 0 : kWeights[i - 1]) + rangeWeightTarget;

Review Comment:
   Wow yeah, both are better (though I like the first). This is the beauty of 
PR reviews haha. When you are 500 lines into a change, who knows what dumb 
things you will write...



##########
lucene/core/src/java/org/apache/lucene/util/WeightedSelector.java:
##########
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.util;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.SplittableRandom;
+
+/**
+ * Adaptive selection algorithm based on the introspective quick select 
algorithm. The quick select
+ * algorithm uses an interpolation variant of Tukey's ninther 
median-of-medians for pivot, and
+ * Bentley-McIlroy 3-way partitioning. For the introspective protection, it 
shuffles the sub-range
+ * if the max recursive depth is exceeded.
+ *
+ * <p>This selection algorithm is fast on most data shapes, especially on 
nearly sorted data, or
+ * when k is close to the boundaries. It runs in linear time on average.
+ *
+ * @lucene.internal
+ */
+public abstract class WeightedSelector {
+
+  // This selector is used repeatedly by the radix selector for sub-ranges of 
less than
+  // 100 entries. This means this selector is also optimized to be fast on 
small ranges.
+  // It uses the variant of medians-of-medians and 3-way partitioning, and 
finishes the
+  // last tiny range (3 entries or less) with a very specialized sort.
+
+  private SplittableRandom random;
+
+  protected abstract long getWeight(int i);
+
+  protected abstract long getValue(int i);
+
+  public final WeightRangeInfo[] select(

Review Comment:
   Absolutely. Was going to go through and add docs, just wanted to make sure 
it was a good direction to go in first. Probably worth doing the benchmarking 
first 🥹



##########
lucene/core/src/java/org/apache/lucene/util/WeightedSelector.java:
##########
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.util;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.SplittableRandom;
+
+/**
+ * Adaptive selection algorithm based on the introspective quick select 
algorithm. The quick select
+ * algorithm uses an interpolation variant of Tukey's ninther 
median-of-medians for pivot, and
+ * Bentley-McIlroy 3-way partitioning. For the introspective protection, it 
shuffles the sub-range
+ * if the max recursive depth is exceeded.
+ *
+ * <p>This selection algorithm is fast on most data shapes, especially on 
nearly sorted data, or
+ * when k is close to the boundaries. It runs in linear time on average.
+ *
+ * @lucene.internal
+ */
+public abstract class WeightedSelector {
+
+  // This selector is used repeatedly by the radix selector for sub-ranges of 
less than
+  // 100 entries. This means this selector is also optimized to be fast on 
small ranges.
+  // It uses the variant of medians-of-medians and 3-way partitioning, and 
finishes the
+  // last tiny range (3 entries or less) with a very specialized sort.
+
+  private SplittableRandom random;
+
+  protected abstract long getWeight(int i);
+
+  protected abstract long getValue(int i);
+
+  public final WeightRangeInfo[] select(
+      int from,
+      int to,
+      long rangeTotalValue,
+      long beforeTotalValue,
+      long rangeWeight,
+      long beforeWeight,
+      double[] kWeights) {
+    WeightRangeInfo[] kIndexResults = new WeightRangeInfo[kWeights.length];
+    Arrays.fill(kIndexResults, new WeightRangeInfo(-1, 0, 0));
+    checkArgs(rangeWeight, beforeWeight, kWeights);
+    select(
+        from,
+        to,
+        rangeTotalValue,
+        beforeTotalValue,
+        rangeWeight,
+        beforeWeight,
+        kWeights,
+        0,
+        kWeights.length,
+        kIndexResults,
+        2 * MathUtil.log(to - from, 2));
+    return kIndexResults;
+  }
+
+  void checkArgs(long rangeWeight, long beforeWeight, double[] kWeights) {
+    if (kWeights.length < 1) {
+      throw new IllegalArgumentException("There must be at least one k to 
select, none given");
+    }
+    Arrays.sort(kWeights);
+    if (kWeights[0] < beforeWeight) {
+      throw new IllegalArgumentException("All kWeights must be >= 
beforeWeight");
+    }
+    if (kWeights[kWeights.length - 1] > beforeWeight + rangeWeight) {
+      throw new IllegalArgumentException("All kWeights must be < beforeWeight 
+ rangeWeight");
+    }
+  }
+
+  // Visible for testing.
+  void select(
+      int from,
+      int to,
+      long rangeTotalValue,
+      long beforeTotalValue,
+      long rangeWeight,
+      long beforeWeight,
+      double[] kWeights,
+      int kFrom,
+      int kTo,
+      WeightRangeInfo[] kIndexResults,
+      int maxDepth) {
+
+    // This code is inspired from IntroSorter#sort, adapted to loop on a 
single partition.
+
+    // For efficiency, we must enter the loop with at least 4 entries to be 
able to skip
+    // some boundary tests during the 3-way partitioning.
+    int size;
+    if ((size = to - from) > 3) {
+
+      if (--maxDepth == -1) {
+        // Max recursion depth exceeded: shuffle (only once) and continue.
+        shuffle(from, to);
+      }
+
+      // Pivot selection based on medians.
+      int last = to - 1;
+      int mid = (from + last) >>> 1;
+      int pivot;
+      if (size <= IntroSorter.SINGLE_MEDIAN_THRESHOLD) {
+        // Select the pivot with a single median around the middle element.
+        // Do not take the median between [from, mid, last] because it hurts 
performance
+        // if the order is descending in conjunction with the 3-way 
partitioning.
+        int range = size >> 2;
+        pivot = median(mid - range, mid, mid + range);
+      } else {
+        // Select the pivot with a variant of the Tukey's ninther median of 
medians.
+        // If k is close to the boundaries, select either the lowest or 
highest median (this variant
+        // is inspired from the interpolation search).
+        int range = size >> 3;
+        int doubleRange = range << 1;
+        int medianFirst = median(from, from + range, from + doubleRange);
+        int medianMiddle = median(mid - range, mid, mid + range);
+        int medianLast = median(last - doubleRange, last - range, last);
+
+        double avgWeight = ((double) rangeWeight) / (to - from);
+        double middleWeight = kWeights[(kFrom + kTo - 1) >> 1];
+        // Approximate the k we are trying to find by assuming an equal weight 
amongst values
+        int middleK = from + (int) ((middleWeight - beforeWeight) / avgWeight);
+        if (middleK - from < range) {
+          // k is close to 'from': select the lowest median.
+          pivot = min(medianFirst, medianMiddle, medianLast);
+        } else if (to - middleK <= range) {
+          // k is close to 'to': select the highest median.
+          pivot = max(medianFirst, medianMiddle, medianLast);
+        } else {
+          // Otherwise select the median of medians.
+          pivot = median(medianFirst, medianMiddle, medianLast);
+        }
+      }
+
+      // Bentley-McIlroy 3-way partitioning.
+      setPivot(pivot);
+      swap(from, pivot);
+      int i = from;
+      int j = to;
+      int p = from + 1;
+      int q = last;
+      long leftTotalValue = 0;
+      long leftWeight = 0;
+      long rightTotalValue = 0;
+      long rightWeight = 0;
+      while (true) {
+        int leftCmp, rightCmp;
+        while ((leftCmp = comparePivot(++i)) > 0) {
+          leftTotalValue += getValue(i);
+          leftWeight += getWeight(i);
+        }
+        while ((rightCmp = comparePivot(--j)) < 0) {
+          rightTotalValue += getValue(j);
+          rightWeight += getWeight(j);
+        }
+        if (i >= j) {
+          if (i == j && rightCmp == 0) {
+            swap(i, p);
+          }
+          break;
+        }
+        swap(i, j);
+        if (rightCmp == 0) {
+          swap(i, p++);
+        } else {
+          leftTotalValue += getValue(i);
+          leftWeight += getWeight(i);
+        }
+        if (leftCmp == 0) {
+          swap(j, q--);
+        } else {
+          rightTotalValue += getValue(j);
+          rightWeight += getWeight(j);
+        }
+      }
+      i = j + 1;
+      for (int l = from; l < p; ) {
+        swap(l++, j--);
+      }
+      for (int l = last; l > q; ) {
+        swap(l--, i++);
+      }
+      long leftWeightEnd = beforeWeight + leftWeight;
+      long rightWeightStart = beforeWeight + rangeWeight - rightWeight;
+
+      // Select the K weight values contained in the bottom and top partitions.
+      int topKFrom = kTo;
+      int bottomKTo = kFrom;
+      for (int ki = kTo - 1; ki >= kFrom; ki--) {
+        if (kWeights[ki] >= rightWeightStart) {
+          topKFrom = ki;
+        }
+        if (kWeights[ki] <= leftWeightEnd) {
+          bottomKTo = ki + 1;
+          break;
+        }
+      }
+      // Recursively select the relevant k-values from the bottom group, if 
there are any k-values
+      // to select there
+      if (bottomKTo > kFrom) {
+        select(
+            from,
+            j + 1,
+            leftTotalValue,
+            beforeTotalValue,
+            leftWeight,
+            beforeWeight,
+            kWeights,
+            kFrom,
+            bottomKTo,
+            kIndexResults,
+            maxDepth);
+      }
+      // Recursively select the relevant k-values from the top group, if there 
are any k-values to
+      // select there
+      if (topKFrom < kTo) {
+        select(
+            i,
+            to,
+            rightTotalValue,
+            beforeTotalValue + rangeTotalValue - rightTotalValue,
+            rightWeight,
+            beforeWeight + rangeWeight - rightWeight,
+            kWeights,
+            topKFrom,
+            kTo,
+            kIndexResults,
+            maxDepth);
+      }
+
+      // Choose the k result indexes for this partition
+      if (bottomKTo < topKFrom) {
+        findKIndexes(
+            j + 1,
+            i,
+            beforeTotalValue + leftTotalValue,
+            beforeWeight + leftWeight,
+            bottomKTo,
+            topKFrom,
+            kWeights,
+            kIndexResults);
+      }
+    }
+
+    // Sort the final tiny range (3 entries or less) with a very specialized 
sort.
+    switch (size) {
+      case 1:
+        kIndexResults[kTo - 1] =
+            new WeightRangeInfo(
+                from, beforeTotalValue + getValue(from), beforeWeight + 
getWeight(from));
+        break;
+      case 2:
+        if (compare(from, from + 1) > 0) {
+          swap(from, from + 1);
+        }
+        findKIndexes(
+            from, from + 2, beforeTotalValue, beforeWeight, kFrom, kTo, 
kWeights, kIndexResults);
+        break;
+      case 3:
+        sort3(from);
+        findKIndexes(
+            from, from + 3, beforeTotalValue, beforeWeight, kFrom, kTo, 
kWeights, kIndexResults);
+        break;
+    }
+  }
+
+  private void findKIndexes(
+      int from,
+      int to,
+      long beforeTotalValue,
+      long beforeWeight,
+      int kFrom,
+      int kTo,
+      double[] kWeights,
+      WeightRangeInfo[] kIndexResults) {
+    long runningWeight = beforeWeight;
+    long runningTotalValue = beforeTotalValue;
+    int kIdx = kFrom;
+    for (int listIdx = from; listIdx < to && kIdx < kTo; listIdx++) {
+      runningWeight += getWeight(listIdx);
+      runningTotalValue += getValue(listIdx);
+      // Skip ahead in the weight list if the same value is used for multiple 
weights, we will only
+      // record a result index for the last weight that matches it.
+      while (++kIdx < kTo && kWeights[kIdx] <= runningWeight) {}
+      if (kWeights[--kIdx] <= runningWeight) {
+        kIndexResults[kIdx] = new WeightRangeInfo(listIdx, runningTotalValue, 
runningWeight);
+        // Now that we have recorded the resultIndex for this weight, go to 
the next weight
+        kIdx++;
+      }
+    }
+  }
+
+  /** Returns the index of the min element among three elements at provided 
indices. */
+  private int min(int i, int j, int k) {
+    if (compare(i, j) <= 0) {
+      return compare(i, k) <= 0 ? i : k;
+    }
+    return compare(j, k) <= 0 ? j : k;
+  }
+
+  /** Returns the index of the max element among three elements at provided 
indices. */
+  private int max(int i, int j, int k) {
+    if (compare(i, j) <= 0) {
+      return compare(j, k) < 0 ? k : j;
+    }
+    return compare(i, k) < 0 ? k : i;
+  }
+
+  /** Copy of {@code IntroSorter#median}. */
+  private int median(int i, int j, int k) {
+    if (compare(i, j) < 0) {
+      if (compare(j, k) <= 0) {
+        return j;
+      }
+      return compare(i, k) < 0 ? k : i;
+    }
+    if (compare(j, k) >= 0) {
+      return j;
+    }
+    return compare(i, k) < 0 ? i : k;
+  }
+
+  /**
+   * Sorts 3 entries starting at from (inclusive). This specialized method is 
more efficient than
+   * calling {@link Sorter#insertionSort(int, int)}.
+   */
+  private void sort3(int from) {
+    final int mid = from + 1;
+    final int last = from + 2;
+    if (compare(from, mid) <= 0) {
+      if (compare(mid, last) > 0) {
+        swap(mid, last);
+        if (compare(from, mid) > 0) {
+          swap(from, mid);
+        }
+      }
+    } else if (compare(mid, last) >= 0) {
+      swap(from, last);
+    } else {
+      swap(from, mid);
+      if (compare(mid, last) > 0) {
+        swap(mid, last);
+      }
+    }
+  }
+
+  /**
+   * Shuffles the entries between from (inclusive) and to (exclusive) with 
Durstenfeld's algorithm.
+   */
+  private void shuffle(int from, int to) {
+    if (this.random == null) {
+      this.random = new SplittableRandom();
+    }
+    SplittableRandom random = this.random;
+    for (int i = to - 1; i > from; i--) {
+      swap(i, random.nextInt(from, i + 1));

Review Comment:
   I haven't even looked at this method. It was straight copied from 
`IntroSelector`.
   
   After doing some research, this seems to be the right way of doing it 
according to the algorithm they specified: 
https://en.wikipedia.org/wiki/Fisher–Yates_shuffle#The_modern_algorithm



##########
lucene/core/src/java/org/apache/lucene/util/WeightedSelector.java:
##########
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.util;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.SplittableRandom;
+
+/**
+ * Adaptive selection algorithm based on the introspective quick select 
algorithm. The quick select
+ * algorithm uses an interpolation variant of Tukey's ninther 
median-of-medians for pivot, and
+ * Bentley-McIlroy 3-way partitioning. For the introspective protection, it 
shuffles the sub-range
+ * if the max recursive depth is exceeded.
+ *
+ * <p>This selection algorithm is fast on most data shapes, especially on 
nearly sorted data, or
+ * when k is close to the boundaries. It runs in linear time on average.
+ *
+ * @lucene.internal
+ */
+public abstract class WeightedSelector {
+
+  // This selector is used repeatedly by the radix selector for sub-ranges of 
less than
+  // 100 entries. This means this selector is also optimized to be fast on 
small ranges.
+  // It uses the variant of medians-of-medians and 3-way partitioning, and 
finishes the
+  // last tiny range (3 entries or less) with a very specialized sort.
+
+  private SplittableRandom random;
+
+  protected abstract long getWeight(int i);
+
+  protected abstract long getValue(int i);
+
+  public final WeightRangeInfo[] select(
+      int from,
+      int to,
+      long rangeTotalValue,
+      long beforeTotalValue,
+      long rangeWeight,
+      long beforeWeight,
+      double[] kWeights) {
+    WeightRangeInfo[] kIndexResults = new WeightRangeInfo[kWeights.length];
+    Arrays.fill(kIndexResults, new WeightRangeInfo(-1, 0, 0));
+    checkArgs(rangeWeight, beforeWeight, kWeights);
+    select(
+        from,
+        to,
+        rangeTotalValue,
+        beforeTotalValue,
+        rangeWeight,
+        beforeWeight,
+        kWeights,
+        0,
+        kWeights.length,
+        kIndexResults,
+        2 * MathUtil.log(to - from, 2));
+    return kIndexResults;
+  }
+
+  void checkArgs(long rangeWeight, long beforeWeight, double[] kWeights) {
+    if (kWeights.length < 1) {
+      throw new IllegalArgumentException("There must be at least one k to 
select, none given");
+    }
+    Arrays.sort(kWeights);
+    if (kWeights[0] < beforeWeight) {
+      throw new IllegalArgumentException("All kWeights must be >= 
beforeWeight");
+    }
+    if (kWeights[kWeights.length - 1] > beforeWeight + rangeWeight) {
+      throw new IllegalArgumentException("All kWeights must be < beforeWeight 
+ rangeWeight");
+    }
+  }
+
+  // Visible for testing.
+  void select(
+      int from,
+      int to,
+      long rangeTotalValue,
+      long beforeTotalValue,
+      long rangeWeight,
+      long beforeWeight,
+      double[] kWeights,
+      int kFrom,
+      int kTo,
+      WeightRangeInfo[] kIndexResults,
+      int maxDepth) {
+
+    // This code is inspired from IntroSorter#sort, adapted to loop on a 
single partition.
+
+    // For efficiency, we must enter the loop with at least 4 entries to be 
able to skip
+    // some boundary tests during the 3-way partitioning.
+    int size;
+    if ((size = to - from) > 3) {
+
+      if (--maxDepth == -1) {
+        // Max recursion depth exceeded: shuffle (only once) and continue.

Review Comment:
   This is from `IntroSelector`, but basically I think it's saying if I've done 
enough recursions in QuickSelect, that means that our data has a really bad 
distribution? So just randomize it a bit and continue. I don't have an opinion 
as I haven't studied it, but hopefully there was research put into the idea?



##########
lucene/core/src/java/org/apache/lucene/util/WeightedSelector.java:
##########
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.util;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.SplittableRandom;
+
+/**
+ * Adaptive selection algorithm based on the introspective quick select 
algorithm. The quick select
+ * algorithm uses an interpolation variant of Tukey's ninther 
median-of-medians for pivot, and
+ * Bentley-McIlroy 3-way partitioning. For the introspective protection, it 
shuffles the sub-range
+ * if the max recursive depth is exceeded.
+ *
+ * <p>This selection algorithm is fast on most data shapes, especially on 
nearly sorted data, or
+ * when k is close to the boundaries. It runs in linear time on average.
+ *
+ * @lucene.internal
+ */
+public abstract class WeightedSelector {
+
+  // This selector is used repeatedly by the radix selector for sub-ranges of 
less than
+  // 100 entries. This means this selector is also optimized to be fast on 
small ranges.
+  // It uses the variant of medians-of-medians and 3-way partitioning, and 
finishes the
+  // last tiny range (3 entries or less) with a very specialized sort.
+
+  private SplittableRandom random;
+
+  protected abstract long getWeight(int i);
+
+  protected abstract long getValue(int i);
+
+  public final WeightRangeInfo[] select(
+      int from,
+      int to,
+      long rangeTotalValue,
+      long beforeTotalValue,
+      long rangeWeight,
+      long beforeWeight,
+      double[] kWeights) {
+    WeightRangeInfo[] kIndexResults = new WeightRangeInfo[kWeights.length];
+    Arrays.fill(kIndexResults, new WeightRangeInfo(-1, 0, 0));
+    checkArgs(rangeWeight, beforeWeight, kWeights);
+    select(
+        from,
+        to,
+        rangeTotalValue,
+        beforeTotalValue,
+        rangeWeight,
+        beforeWeight,
+        kWeights,
+        0,
+        kWeights.length,
+        kIndexResults,
+        2 * MathUtil.log(to - from, 2));
+    return kIndexResults;
+  }
+
+  void checkArgs(long rangeWeight, long beforeWeight, double[] kWeights) {
+    if (kWeights.length < 1) {
+      throw new IllegalArgumentException("There must be at least one k to 
select, none given");
+    }
+    Arrays.sort(kWeights);
+    if (kWeights[0] < beforeWeight) {
+      throw new IllegalArgumentException("All kWeights must be >= 
beforeWeight");
+    }
+    if (kWeights[kWeights.length - 1] > beforeWeight + rangeWeight) {
+      throw new IllegalArgumentException("All kWeights must be < beforeWeight 
+ rangeWeight");
+    }
+  }
+
+  // Visible for testing.
+  void select(

Review Comment:
   Yeah, the 3-way partitioning was also quite confusing to me until I looked 
it up. And even then, the code is still quite hard to understand. I copied the 
default implementation from `IntroSelector`, then modified it to support 
multi-select, and select by cumulative weight, not by ordinal. So a lot of the 
complexity/confusion I can't necessarily speak to. Maybe this would be clearer 
if in the Javadocs of the class, it called out `IntroSelector` as the base 
algorithm?



##########
lucene/core/src/java/org/apache/lucene/util/WeightedSelector.java:
##########
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.util;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.SplittableRandom;
+
+/**
+ * Adaptive selection algorithm based on the introspective quick select 
algorithm. The quick select
+ * algorithm uses an interpolation variant of Tukey's ninther 
median-of-medians for pivot, and
+ * Bentley-McIlroy 3-way partitioning. For the introspective protection, it 
shuffles the sub-range
+ * if the max recursive depth is exceeded.
+ *
+ * <p>This selection algorithm is fast on most data shapes, especially on 
nearly sorted data, or
+ * when k is close to the boundaries. It runs in linear time on average.
+ *
+ * @lucene.internal
+ */
+public abstract class WeightedSelector {
+
+  // This selector is used repeatedly by the radix selector for sub-ranges of 
less than
+  // 100 entries. This means this selector is also optimized to be fast on 
small ranges.
+  // It uses the variant of medians-of-medians and 3-way partitioning, and 
finishes the
+  // last tiny range (3 entries or less) with a very specialized sort.
+
+  private SplittableRandom random;
+
+  protected abstract long getWeight(int i);
+
+  protected abstract long getValue(int i);
+
+  public final WeightRangeInfo[] select(
+      int from,
+      int to,
+      long rangeTotalValue,
+      long beforeTotalValue,
+      long rangeWeight,
+      long beforeWeight,
+      double[] kWeights) {

Review Comment:
   That's a very fair point. I struggled naming this. Basically the `k` prefix 
is for choosing where to select. So `kWeights` is the weight-cutoffs that you 
want to select. if you have a total weight of 100 and want to group into 5, 
then `kWeights` would be `[20,40,60,80,100]`.  Very open to better naming 
anywhere!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org
For additional commands, e-mail: issues-h...@lucene.apache.org

Reply via email to