Skip to content

Commit 54c602b

Browse files
authored
Rollup merge of rust-lang#55448 - Mokosha:SortAtIndex, r=bluss
Add 'partition_at_index/_by/_by_key' for slices. This is an analog to C++'s std::nth_element (a.k.a. quickselect). Corresponds to tracking bug rust-lang#55300.
2 parents 428943c + 3f306db commit 54c602b

File tree

4 files changed

+355
-0
lines changed

4 files changed

+355
-0
lines changed

src/libcore/slice/mod.rs

+147
Original file line numberDiff line numberDiff line change
@@ -1585,6 +1585,153 @@ impl<T> [T] {
15851585
sort::quicksort(self, |a, b| f(a).lt(&f(b)));
15861586
}
15871587

1588+
/// Reorder the slice such that the element at `index` is at its final sorted position.
1589+
///
1590+
/// This reordering has the additional property that any value at position `i < index` will be
1591+
/// less than or equal to any value at a position `j > index`. Additionally, this reordering is
1592+
/// unstable (i.e. any number of equal elements may end up at position `index`), in-place
1593+
/// (i.e. does not allocate), and `O(n)` worst-case. This function is also/ known as "kth
1594+
/// element" in other libraries. It returns a triplet of the following values: all elements less
1595+
/// than the one at the given index, the value at the given index, and all elements greater than
1596+
/// the one at the given index.
1597+
///
1598+
/// # Current implementation
1599+
///
1600+
/// The current algorithm is based on the quickselect portion of the same quicksort algorithm
1601+
/// used for [`sort_unstable`].
1602+
///
1603+
/// [`sort_unstable`]: #method.sort_unstable
1604+
///
1605+
/// # Panics
1606+
///
1607+
/// Panics when `index >= len()`, meaning it always panics on empty slices.
1608+
///
1609+
/// # Examples
1610+
///
1611+
/// ```
1612+
/// #![feature(slice_partition_at_index)]
1613+
///
1614+
/// let mut v = [-5i32, 4, 1, -3, 2];
1615+
///
1616+
/// // Find the median
1617+
/// v.partition_at_index(2);
1618+
///
1619+
/// // We are only guaranteed the slice will be one of the following, based on the way we sort
1620+
/// // about the specified index.
1621+
/// assert!(v == [-3, -5, 1, 2, 4] ||
1622+
/// v == [-5, -3, 1, 2, 4] ||
1623+
/// v == [-3, -5, 1, 4, 2] ||
1624+
/// v == [-5, -3, 1, 4, 2]);
1625+
/// ```
1626+
#[unstable(feature = "slice_partition_at_index", issue = "55300")]
1627+
#[inline]
1628+
pub fn partition_at_index(&mut self, index: usize) -> (&mut [T], &mut T, &mut [T])
1629+
where T: Ord
1630+
{
1631+
let mut f = |a: &T, b: &T| a.lt(b);
1632+
sort::partition_at_index(self, index, &mut f)
1633+
}
1634+
1635+
/// Reorder the slice with a comparator function such that the element at `index` is at its
1636+
/// final sorted position.
1637+
///
1638+
/// This reordering has the additional property that any value at position `i < index` will be
1639+
/// less than or equal to any value at a position `j > index` using the comparator function.
1640+
/// Additionally, this reordering is unstable (i.e. any number of equal elements may end up at
1641+
/// position `index`), in-place (i.e. does not allocate), and `O(n)` worst-case. This function
1642+
/// is also known as "kth element" in other libraries. It returns a triplet of the following
1643+
/// values: all elements less than the one at the given index, the value at the given index,
1644+
/// and all elements greater than the one at the given index, using the provided comparator
1645+
/// function.
1646+
///
1647+
/// # Current implementation
1648+
///
1649+
/// The current algorithm is based on the quickselect portion of the same quicksort algorithm
1650+
/// used for [`sort_unstable`].
1651+
///
1652+
/// [`sort_unstable`]: #method.sort_unstable
1653+
///
1654+
/// # Panics
1655+
///
1656+
/// Panics when `index >= len()`, meaning it always panics on empty slices.
1657+
///
1658+
/// # Examples
1659+
///
1660+
/// ```
1661+
/// #![feature(slice_partition_at_index)]
1662+
///
1663+
/// let mut v = [-5i32, 4, 1, -3, 2];
1664+
///
1665+
/// // Find the median as if the slice were sorted in descending order.
1666+
/// v.partition_at_index_by(2, |a, b| b.cmp(a));
1667+
///
1668+
/// // We are only guaranteed the slice will be one of the following, based on the way we sort
1669+
/// // about the specified index.
1670+
/// assert!(v == [2, 4, 1, -5, -3] ||
1671+
/// v == [2, 4, 1, -3, -5] ||
1672+
/// v == [4, 2, 1, -5, -3] ||
1673+
/// v == [4, 2, 1, -3, -5]);
1674+
/// ```
1675+
#[unstable(feature = "slice_partition_at_index", issue = "55300")]
1676+
#[inline]
1677+
pub fn partition_at_index_by<F>(&mut self, index: usize, mut compare: F)
1678+
-> (&mut [T], &mut T, &mut [T])
1679+
where F: FnMut(&T, &T) -> Ordering
1680+
{
1681+
let mut f = |a: &T, b: &T| compare(a, b) == Less;
1682+
sort::partition_at_index(self, index, &mut f)
1683+
}
1684+
1685+
/// Reorder the slice with a key extraction function such that the element at `index` is at its
1686+
/// final sorted position.
1687+
///
1688+
/// This reordering has the additional property that any value at position `i < index` will be
1689+
/// less than or equal to any value at a position `j > index` using the key extraction function.
1690+
/// Additionally, this reordering is unstable (i.e. any number of equal elements may end up at
1691+
/// position `index`), in-place (i.e. does not allocate), and `O(n)` worst-case. This function
1692+
/// is also known as "kth element" in other libraries. It returns a triplet of the following
1693+
/// values: all elements less than the one at the given index, the value at the given index, and
1694+
/// all elements greater than the one at the given index, using the provided key extraction
1695+
/// function.
1696+
///
1697+
/// # Current implementation
1698+
///
1699+
/// The current algorithm is based on the quickselect portion of the same quicksort algorithm
1700+
/// used for [`sort_unstable`].
1701+
///
1702+
/// [`sort_unstable`]: #method.sort_unstable
1703+
///
1704+
/// # Panics
1705+
///
1706+
/// Panics when `index >= len()`, meaning it always panics on empty slices.
1707+
///
1708+
/// # Examples
1709+
///
1710+
/// ```
1711+
/// #![feature(slice_partition_at_index)]
1712+
///
1713+
/// let mut v = [-5i32, 4, 1, -3, 2];
1714+
///
1715+
/// // Return the median as if the array were sorted according to absolute value.
1716+
/// v.partition_at_index_by_key(2, |a| a.abs());
1717+
///
1718+
/// // We are only guaranteed the slice will be one of the following, based on the way we sort
1719+
/// // about the specified index.
1720+
/// assert!(v == [1, 2, -3, 4, -5] ||
1721+
/// v == [1, 2, -3, -5, 4] ||
1722+
/// v == [2, 1, -3, 4, -5] ||
1723+
/// v == [2, 1, -3, -5, 4]);
1724+
/// ```
1725+
#[unstable(feature = "slice_partition_at_index", issue = "55300")]
1726+
#[inline]
1727+
pub fn partition_at_index_by_key<K, F>(&mut self, index: usize, mut f: F)
1728+
-> (&mut [T], &mut T, &mut [T])
1729+
where F: FnMut(&T) -> K, K: Ord
1730+
{
1731+
let mut g = |a: &T, b: &T| f(a).lt(&f(b));
1732+
sort::partition_at_index(self, index, &mut g)
1733+
}
1734+
15881735
/// Moves all consecutive repeated elements to the end of the slice according to the
15891736
/// [`PartialEq`] trait implementation.
15901737
///

src/libcore/slice/sort.rs

+89
Original file line numberDiff line numberDiff line change
@@ -691,3 +691,92 @@ pub fn quicksort<T, F>(v: &mut [T], mut is_less: F)
691691

692692
recurse(v, &mut is_less, None, limit);
693693
}
694+
695+
fn partition_at_index_loop<'a, T, F>( mut v: &'a mut [T], mut index: usize, is_less: &mut F
696+
, mut pred: Option<&'a T>) where F: FnMut(&T, &T) -> bool
697+
{
698+
loop {
699+
// For slices of up to this length it's probably faster to simply sort them.
700+
const MAX_INSERTION: usize = 10;
701+
if v.len() <= MAX_INSERTION {
702+
insertion_sort(v, is_less);
703+
return;
704+
}
705+
706+
// Choose a pivot
707+
let (pivot, _) = choose_pivot(v, is_less);
708+
709+
// If the chosen pivot is equal to the predecessor, then it's the smallest element in the
710+
// slice. Partition the slice into elements equal to and elements greater than the pivot.
711+
// This case is usually hit when the slice contains many duplicate elements.
712+
if let Some(p) = pred {
713+
if !is_less(p, &v[pivot]) {
714+
let mid = partition_equal(v, pivot, is_less);
715+
716+
// If we've passed our index, then we're good.
717+
if mid > index {
718+
return;
719+
}
720+
721+
// Otherwise, continue sorting elements greater than the pivot.
722+
v = &mut v[mid..];
723+
index = index - mid;
724+
pred = None;
725+
continue;
726+
}
727+
}
728+
729+
let (mid, _) = partition(v, pivot, is_less);
730+
731+
// Split the slice into `left`, `pivot`, and `right`.
732+
let (left, right) = {v}.split_at_mut(mid);
733+
let (pivot, right) = right.split_at_mut(1);
734+
let pivot = &pivot[0];
735+
736+
if mid < index {
737+
v = right;
738+
index = index - mid - 1;
739+
pred = Some(pivot);
740+
} else if mid > index {
741+
v = left;
742+
} else {
743+
// If mid == index, then we're done, since partition() guaranteed that all elements
744+
// after mid are greater than or equal to mid.
745+
return;
746+
}
747+
}
748+
}
749+
750+
pub fn partition_at_index<T, F>(v: &mut [T], index: usize, mut is_less: F)
751+
-> (&mut [T], &mut T, &mut [T]) where F: FnMut(&T, &T) -> bool
752+
{
753+
use cmp::Ordering::Less;
754+
use cmp::Ordering::Greater;
755+
756+
if index >= v.len() {
757+
panic!("partition_at_index index {} greater than length of slice {}", index, v.len());
758+
}
759+
760+
if mem::size_of::<T>() == 0 {
761+
// Sorting has no meaningful behavior on zero-sized types. Do nothing.
762+
} else if index == v.len() - 1 {
763+
// Find max element and place it in the last position of the array. We're free to use
764+
// `unwrap()` here because we know v must not be empty.
765+
let (max_index, _) = v.iter().enumerate().max_by(
766+
|&(_, x), &(_, y)| if is_less(x, y) { Less } else { Greater }).unwrap();
767+
v.swap(max_index, index);
768+
} else if index == 0 {
769+
// Find min element and place it in the first position of the array. We're free to use
770+
// `unwrap()` here because we know v must not be empty.
771+
let (min_index, _) = v.iter().enumerate().min_by(
772+
|&(_, x), &(_, y)| if is_less(x, y) { Less } else { Greater }).unwrap();
773+
v.swap(min_index, index);
774+
} else {
775+
partition_at_index_loop(v, index, &mut is_less, None);
776+
}
777+
778+
let (left, right) = v.split_at_mut(index);
779+
let (pivot, right) = right.split_at_mut(1);
780+
let pivot = &mut pivot[0];
781+
(left, pivot, right)
782+
}

src/libcore/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#![feature(raw)]
1919
#![feature(slice_patterns)]
2020
#![feature(sort_internals)]
21+
#![feature(slice_partition_at_index)]
2122
#![feature(specialization)]
2223
#![feature(step_trait)]
2324
#![feature(str_internals)]

src/libcore/tests/slice.rs

+118
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,124 @@ fn sort_unstable() {
10931093
assert!(v == [0xDEADBEEF]);
10941094
}
10951095

1096+
#[test]
1097+
#[cfg(not(target_arch = "wasm32"))]
1098+
#[cfg(not(miri))] // Miri does not support entropy
1099+
fn partition_at_index() {
1100+
use core::cmp::Ordering::{Equal, Greater, Less};
1101+
use rand::rngs::SmallRng;
1102+
use rand::seq::SliceRandom;
1103+
use rand::{FromEntropy, Rng};
1104+
1105+
let mut rng = SmallRng::from_entropy();
1106+
1107+
for len in (2..21).chain(500..501) {
1108+
let mut orig = vec![0; len];
1109+
1110+
for &modulus in &[5, 10, 1000] {
1111+
for _ in 0..10 {
1112+
for i in 0..len {
1113+
orig[i] = rng.gen::<i32>() % modulus;
1114+
}
1115+
1116+
let v_sorted = {
1117+
let mut v = orig.clone();
1118+
v.sort();
1119+
v
1120+
};
1121+
1122+
// Sort in default order.
1123+
for pivot in 0..len {
1124+
let mut v = orig.clone();
1125+
v.partition_at_index(pivot);
1126+
1127+
assert_eq!(v_sorted[pivot], v[pivot]);
1128+
for i in 0..pivot {
1129+
for j in pivot..len {
1130+
assert!(v[i] <= v[j]);
1131+
}
1132+
}
1133+
}
1134+
1135+
// Sort in ascending order.
1136+
for pivot in 0..len {
1137+
let mut v = orig.clone();
1138+
let (left, pivot, right) = v.partition_at_index_by(pivot, |a, b| a.cmp(b));
1139+
1140+
assert_eq!(left.len() + right.len(), len - 1);
1141+
1142+
for l in left {
1143+
assert!(l <= pivot);
1144+
for r in right.iter_mut() {
1145+
assert!(l <= r);
1146+
assert!(pivot <= r);
1147+
}
1148+
}
1149+
}
1150+
1151+
// Sort in descending order.
1152+
let sort_descending_comparator = |a: &i32, b: &i32| b.cmp(a);
1153+
let v_sorted_descending = {
1154+
let mut v = orig.clone();
1155+
v.sort_by(sort_descending_comparator);
1156+
v
1157+
};
1158+
1159+
for pivot in 0..len {
1160+
let mut v = orig.clone();
1161+
v.partition_at_index_by(pivot, sort_descending_comparator);
1162+
1163+
assert_eq!(v_sorted_descending[pivot], v[pivot]);
1164+
for i in 0..pivot {
1165+
for j in pivot..len {
1166+
assert!(v[j] <= v[i]);
1167+
}
1168+
}
1169+
}
1170+
}
1171+
}
1172+
}
1173+
1174+
// Sort at index using a completely random comparison function.
1175+
// This will reorder the elements *somehow*, but won't panic.
1176+
let mut v = [0; 500];
1177+
for i in 0..v.len() {
1178+
v[i] = i as i32;
1179+
}
1180+
1181+
for pivot in 0..v.len() {
1182+
v.partition_at_index_by(pivot, |_, _| *[Less, Equal, Greater].choose(&mut rng).unwrap());
1183+
v.sort();
1184+
for i in 0..v.len() {
1185+
assert_eq!(v[i], i as i32);
1186+
}
1187+
}
1188+
1189+
// Should not panic.
1190+
[(); 10].partition_at_index(0);
1191+
[(); 10].partition_at_index(5);
1192+
[(); 10].partition_at_index(9);
1193+
[(); 100].partition_at_index(0);
1194+
[(); 100].partition_at_index(50);
1195+
[(); 100].partition_at_index(99);
1196+
1197+
let mut v = [0xDEADBEEFu64];
1198+
v.partition_at_index(0);
1199+
assert!(v == [0xDEADBEEF]);
1200+
}
1201+
1202+
#[test]
1203+
#[should_panic(expected = "index 0 greater than length of slice")]
1204+
fn partition_at_index_zero_length() {
1205+
[0i32; 0].partition_at_index(0);
1206+
}
1207+
1208+
#[test]
1209+
#[should_panic(expected = "index 20 greater than length of slice")]
1210+
fn partition_at_index_past_length() {
1211+
[0i32; 10].partition_at_index(20);
1212+
}
1213+
10961214
pub mod memchr {
10971215
use core::slice::memchr::{memchr, memrchr};
10981216

0 commit comments

Comments
 (0)