Skip to content

Commit 359dc0c

Browse files
committed
Avoid worst case complexity for equal elements.
* Delegate single-index invocation to non-bulk implementation. * Non-bulk implementation skips recursion if `index == pivot`.
1 parent 23782e7 commit 359dc0c

File tree

2 files changed

+21
-86
lines changed

2 files changed

+21
-86
lines changed

src/sort.rs

Lines changed: 20 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -366,17 +366,23 @@ where
366366
A: Ord + Clone,
367367
S: DataMut<Elem = A>,
368368
{
369-
if indexes.is_empty() {
370-
return IndexMap::new();
371-
}
372-
373-
// Since `!indexes.is_empty()` and indexes must be in-bounds, `array` must
374-
// be non-empty.
375-
let mut values = vec![array[0].clone(); indexes.len()];
376-
_get_many_from_sorted_mut_unchecked(array.view_mut(), &mut indexes.to_owned(), &mut values);
369+
match indexes.len() {
370+
0 => IndexMap::new(),
371+
1 => IndexMap::from([(indexes[0], array.get_from_sorted_mut(indexes[0]))]),
372+
_ => {
373+
// Since `!indexes.is_empty()` and indexes must be in-bounds, `array` must
374+
// be non-empty.
375+
let mut values = vec![array[0].clone(); indexes.len()];
376+
_get_many_from_sorted_mut_unchecked(
377+
array.view_mut(),
378+
&mut indexes.to_owned(),
379+
&mut values,
380+
);
377381

378-
// We convert the vector to a more search-friendly `IndexMap`.
379-
indexes.iter().cloned().zip(values.into_iter()).collect()
382+
// We convert the vector to a more search-friendly `IndexMap`.
383+
indexes.iter().cloned().zip(values.into_iter()).collect()
384+
}
385+
}
380386
}
381387

382388
/// This is the recursive portion of `get_many_from_sorted_mut_unchecked`.
@@ -420,81 +426,10 @@ fn _get_many_from_sorted_mut_unchecked<A>(
420426
// Sorted sample of 5 equally spaced elements around the center.
421427
let mut sample = [0; 5];
422428
sample_mut(&mut array, &mut sample);
423-
let (lower_index, upper_index) = if indexes.len() == 1 {
424-
// Adapt pivot sampling to relative sought rank and switch from dual-pivot to single-pivot
425-
// partitioning for extreme sought ranks.
426-
let sought_rank = indexes[0] as f64 / n as f64;
427-
if (0.036..=0.964).contains(&sought_rank) {
428-
if sought_rank <= 0.5 {
429-
if sought_rank <= 0.153 {
430-
(0, 1) // (0, 0, 3)
431-
} else {
432-
(0, 2) // (0, 1, 2)
433-
}
434-
} else {
435-
if sought_rank <= 0.847 {
436-
(2, 4) // (2, 1, 0)
437-
} else {
438-
(3, 4) // (3, 0, 0)
439-
}
440-
}
441-
} else {
442-
let pivot_index = if sought_rank <= 0.5 {
443-
0 // (0, 4)
444-
} else {
445-
4 // (4, 0)
446-
};
447-
448-
// We partition the array with respect to the pivot value. The pivot value moves to the
449-
// new `pivot_index`.
450-
//
451-
// Elements strictly less than the pivot value have indexes < `pivot_index`.
452-
//
453-
// Elements greater than or equal the pivot value have indexes > `pivot_index`.
454-
let pivot_index = array.partition_mut(sample[pivot_index]);
455-
let (found_exact, split_index) = match indexes.binary_search(&pivot_index) {
456-
Ok(index) => (true, index),
457-
Err(index) => (false, index),
458-
};
459-
let (lower_indexes, upper_indexes) = indexes.split_at_mut(split_index);
460-
let (lower_values, upper_values) = values.split_at_mut(split_index);
461-
let (upper_indexes, upper_values) = if found_exact {
462-
upper_values[0] = array[pivot_index].clone(); // Write exactly found value.
463-
(&mut upper_indexes[1..], &mut upper_values[1..])
464-
} else {
465-
(upper_indexes, upper_values)
466-
};
467-
468-
// We search recursively for the values corresponding to indexes strictly less than
469-
// `pivot_index` in the lower partition.
470-
maybe_grow(RED_ZONE, STACK_SIZE, || {
471-
_get_many_from_sorted_mut_unchecked(
472-
array.slice_axis_mut(Axis(0), Slice::from(..pivot_index)),
473-
lower_indexes,
474-
lower_values,
475-
);
476-
});
477-
478-
// We search recursively for the values corresponding to indexes greater than or equal
479-
// `pivot_index` in the upper partition. Since only the upper partition of the array is
480-
// passed in, the indexes need to be shifted by length of the lower partition.
481-
upper_indexes.iter_mut().for_each(|x| *x -= pivot_index + 1);
482-
maybe_grow(RED_ZONE, STACK_SIZE, || {
483-
_get_many_from_sorted_mut_unchecked(
484-
array.slice_axis_mut(Axis(0), Slice::from(pivot_index + 1..)),
485-
upper_indexes,
486-
upper_values,
487-
);
488-
});
489-
490-
return;
491-
}
492-
} else {
493-
// Since there is no single sought rank to adapt pivot sampling to, the recommended skewed
494-
// pivot sampling of dual-pivot Quicksort is used in the assumption that multiple indexes
495-
// change characteristics from Quickselect towards Quicksort.
496-
(0, 2) // (0, 1, 2)
497-
};
429+
// Since there is no single sought rank to adapt pivot sampling to, the recommended skewed
430+
// pivot sampling of dual-pivot Quicksort is used in the assumption that multiple indexes
431+
// change characteristics from Quickselect towards Quicksort.
432+
let (lower_index, upper_index) = (0, 2); // (0, 1, 2)
498433

499434
// We partition the array with respect to the two pivot values. The pivot values move to the new
500435
// `lower_index` and `upper_index`.

tests/sort.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ fn test_dual_partition_mut() {
6767

6868
#[test]
6969
fn test_quantile_mut_with_large_array_of_equal_floats() {
70-
let mut array: Array1<N64> = Array1::ones(100_000);
70+
let mut array: Array1<N64> = Array1::ones(1_000_000);
7171
array.quantile_mut(n64(0.5), &Linear).unwrap();
7272
}
7373

0 commit comments

Comments
 (0)