@@ -110,25 +110,16 @@ private static void sortAndValidate(double[] upperBounds, long[] counts) {
110110 validate (upperBounds , counts );
111111 }
112112
113+ /**
114+ * Sorts upperBounds and counts in place using introspective quicksort.
115+ *
116+ * <p>Algorithm: 3-way quicksort with insertion sort for tiny partitions and heapsort fallback at
117+ * the recursion depth limit. Parallel arrays are swapped in lockstep.
118+ *
119+ * <p>Complexity: O(n log n) average and worst case.
120+ */
113121 private static void sort (double [] upperBounds , long [] counts ) {
114- // Bubblesort. Should be efficient here as in most cases upperBounds is already sorted.
115- int n = upperBounds .length ;
116- for (int i = 0 ; i < n - 1 ; i ++) {
117- for (int j = 0 ; j < n - i - 1 ; j ++) {
118- if (upperBounds [j ] > upperBounds [j + 1 ]) {
119- swap (j , j + 1 , upperBounds , counts );
120- }
121- }
122- }
123- }
124-
125- private static void swap (int i , int j , double [] upperBounds , long [] counts ) {
126- double tmpDouble = upperBounds [j ];
127- upperBounds [j ] = upperBounds [i ];
128- upperBounds [i ] = tmpDouble ;
129- long tmpLong = counts [j ];
130- counts [j ] = counts [i ];
131- counts [i ] = tmpLong ;
122+ DoubleArraySorter .sort (upperBounds , counts );
132123 }
133124
134125 private static void validate (double [] upperBounds , long [] counts ) {
@@ -224,4 +215,157 @@ public ClassicHistogramBuckets build() {
224215 return ClassicHistogramBuckets .of (upperBounds , counts );
225216 }
226217 }
218+
219+ /**
220+ * In-place introsort for {@code upperBounds} and parallel {@code counts}.
221+ *
222+ * <p>Uses 3-way quicksort partitioning for large ranges, insertion sort for tiny ranges, and a
223+ * heapsort fallback at the recursion-depth limit to guarantee O(n log n) worst-case complexity.
224+ */
225+ private static final class DoubleArraySorter {
226+
227+ private static final int INSERTION_SORT_THRESHOLD = 24 ;
228+
229+ private static void sort (double [] upperBounds , long [] counts ) {
230+ int right = upperBounds .length - 1 ;
231+ if (right <= 0 ) {
232+ return ;
233+ }
234+ introSort (upperBounds , counts , 0 , right , depthLimit (upperBounds .length ));
235+ }
236+
237+ private static void introSort (
238+ double [] upperBounds , long [] counts , int left , int right , int depthLimit ) {
239+ while (left < right ) {
240+ if (right - left + 1 <= INSERTION_SORT_THRESHOLD ) {
241+ insertionSort (upperBounds , counts , left , right );
242+ return ;
243+ }
244+ if (depthLimit == 0 ) {
245+ heapSort (upperBounds , counts , left , right );
246+ return ;
247+ }
248+ depthLimit --;
249+
250+ int mid = left + ((right - left ) >>> 1 );
251+ int pivotIndex = medianOf3 (upperBounds , left , mid , right );
252+ double pivot = upperBounds [pivotIndex ];
253+
254+ int lt = left ;
255+ int i = left ;
256+ int gt = right ;
257+ while (i <= gt ) {
258+ int cmp = compare (upperBounds [i ], pivot );
259+ if (cmp < 0 ) {
260+ swap (i , lt , upperBounds , counts );
261+ i ++;
262+ lt ++;
263+ } else if (cmp > 0 ) {
264+ swap (i , gt , upperBounds , counts );
265+ gt --;
266+ } else {
267+ i ++;
268+ }
269+ }
270+
271+ if (lt - left < right - gt ) {
272+ introSort (upperBounds , counts , left , lt - 1 , depthLimit );
273+ left = gt + 1 ;
274+ } else {
275+ introSort (upperBounds , counts , gt + 1 , right , depthLimit );
276+ right = lt - 1 ;
277+ }
278+ }
279+ }
280+
281+ private static void insertionSort (double [] upperBounds , long [] counts , int left , int right ) {
282+ for (int i = left + 1 ; i <= right ; i ++) {
283+ double upperBound = upperBounds [i ];
284+ long count = counts [i ];
285+ int j = i - 1 ;
286+ while (j >= left && compare (upperBounds [j ], upperBound ) > 0 ) {
287+ upperBounds [j + 1 ] = upperBounds [j ];
288+ counts [j + 1 ] = counts [j ];
289+ j --;
290+ }
291+ upperBounds [j + 1 ] = upperBound ;
292+ counts [j + 1 ] = count ;
293+ }
294+ }
295+
296+ private static void heapSort (double [] upperBounds , long [] counts , int left , int right ) {
297+ int size = right - left + 1 ;
298+ for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i --) {
299+ siftDown (upperBounds , counts , left , i , size );
300+ }
301+ for (int end = size - 1 ; end > 0 ; end --) {
302+ swap (left , left + end , upperBounds , counts );
303+ siftDown (upperBounds , counts , left , 0 , end );
304+ }
305+ }
306+
307+ private static void siftDown (
308+ double [] upperBounds , long [] counts , int base , int root , int size ) {
309+ while (true ) {
310+ int child = (root << 1 ) + 1 ;
311+ if (child >= size ) {
312+ return ;
313+ }
314+ int rightChild = child + 1 ;
315+ if (rightChild < size
316+ && compare (upperBounds [base + child ], upperBounds [base + rightChild ]) < 0 ) {
317+ child = rightChild ;
318+ }
319+ if (compare (upperBounds [base + root ], upperBounds [base + child ]) >= 0 ) {
320+ return ;
321+ }
322+ swap (base + root , base + child , upperBounds , counts );
323+ root = child ;
324+ }
325+ }
326+
327+ private static int depthLimit (int length ) {
328+ int result = 0 ;
329+ while (length > 1 ) {
330+ result ++;
331+ length >>>= 1 ;
332+ }
333+ return result << 1 ;
334+ }
335+
336+ private static int medianOf3 (double [] upperBounds , int i , int j , int k ) {
337+ if (compare (upperBounds [i ], upperBounds [j ]) > 0 ) {
338+ int tmp = i ;
339+ i = j ;
340+ j = tmp ;
341+ }
342+ if (compare (upperBounds [j ], upperBounds [k ]) > 0 ) {
343+ int tmp = j ;
344+ j = k ;
345+ k = tmp ;
346+ }
347+ if (compare (upperBounds [i ], upperBounds [j ]) > 0 ) {
348+ int tmp = i ;
349+ i = j ;
350+ j = tmp ;
351+ }
352+ return j ;
353+ }
354+
355+ private static int compare (double a , double b ) {
356+ return Double .compare (a , b );
357+ }
358+
359+ private static void swap (int i , int j , double [] upperBounds , long [] counts ) {
360+ if (i == j ) {
361+ return ;
362+ }
363+ double upperBound = upperBounds [i ];
364+ upperBounds [i ] = upperBounds [j ];
365+ upperBounds [j ] = upperBound ;
366+ long count = counts [i ];
367+ counts [i ] = counts [j ];
368+ counts [j ] = count ;
369+ }
370+ }
227371}
0 commit comments