Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 210 additions & 0 deletions library/core/src/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3242,6 +3242,216 @@ impl<T> [T] {
sort::unstable::sort(self, &mut |a, b| f(a).lt(&f(b)));
}

/// Partially sorts the slice in ascending order **without** preserving the initial order of equal elements.
///
/// Upon completion, the elements in the specified `range` will be the elements of the slice
/// as if the whole slice were sorted in ascending order.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing the guarantees I mentioned in my comment on the ACP about the elements that come before and after the sorted range.

///
/// This sort is unstable (i.e., may reorder equal elements), in-place (i.e., does not
/// allocate), and *O*(*n* + *k* \* log(*k*)) worst-case.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not specified what k is.

///
/// If the implementation of [`Ord`] for `T` does not implement a [total order], the function
/// may panic; even if the function exits normally, the resulting order of elements in the slice
/// is unspecified. See also the note on panicking below.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to repeat the entire comment with examples from sort_unstable_by here, I think we can just refer the reader to its comment. This concerns this paragraph as well as all following paragraphs up until # Panics.

///
/// For example `|a, b| (a - b).cmp(a)` is a comparison function that is neither transitive nor
/// reflexive nor total, `a < b < c < a` with `a = 1, b = 2, c = 3`. For more information and
/// examples see the [`Ord`] documentation.
///
/// All original elements will remain in the slice and any possible modifications via interior
/// mutability are observed in the input. Same is true if the implementation of [`Ord`] for `T` panics.
///
/// Sorting types that only implement [`PartialOrd`] such as [`f32`] and [`f64`] require
/// additional precautions. For example, `f32::NAN != f32::NAN`, which doesn't fulfill the
/// reflexivity requirement of [`Ord`]. By using an alternative comparison function with
/// `slice::partial_sort_unstable_by` such as [`f32::total_cmp`] or [`f64::total_cmp`] that defines a
/// [total order] users can sort slices containing floating-point values. Alternatively, if all
/// values in the slice are guaranteed to be in a subset for which [`PartialOrd::partial_cmp`]
/// forms a [total order], it's possible to sort the slice with `partial_sort_unstable_by(|a, b|
/// a.partial_cmp(b).unwrap())`.
///
/// # Panics
///
/// May panic if the implementation of [`Ord`] for `T` does not implement a [total order], or if
/// the [`Ord`] implementation panics, or if the specified range is out of bounds.
///
/// # Examples
///
/// ```
/// #![feature(slice_partial_sort_unstable)]
///
/// let mut v = [4, -5, 1, -3, 2];
///
/// // empty range, nothing changed
/// v.partial_sort_unstable(0..0);
/// assert_eq!(v, [4, -5, 1, -3, 2]);
///
/// // single element range, same as select_nth_unstable
/// v.partial_sort_unstable(2..3);
/// assert_eq!(v[2], 1);
///
/// // partial sort a subrange
/// v.partial_sort_unstable(1..4);
/// assert_eq!(&v[1..4], [-3, 1, 2]);
///
/// // partial sort the whole range, same as sort_unstable
/// v.partial_sort_unstable(..);
/// assert_eq!(v, [-5, -3, 1, 2, 4]);
/// ```
///
/// [total order]: https://en.wikipedia.org/wiki/Total_order
#[unstable(feature = "slice_partial_sort_unstable", issue = "149046")]
#[inline]
pub fn partial_sort_unstable<R>(&mut self, range: R)
where
T: Ord,
R: RangeBounds<usize>,
{
self.partial_sort_unstable_by(range, T::cmp);
}

/// Partially sorts the slice in ascending order with a comparison function, **without**
/// preserving the initial order of equal elements.
///
/// Upon completion, the elements in the specified `range` will be the elements of the slice
/// as if the whole slice were sorted in ascending order.
///
/// This sort is unstable (i.e., may reorder equal elements), in-place (i.e., does not
/// allocate), and *O*(*n* + *k* \* log(*k*)) worst-case.
///
/// If the comparison function `compare` does not implement a [total order], the function
/// may panic; even if the function exits normally, the resulting order of elements in the slice
/// is unspecified. See also the note on panicking below.
///
/// For example `|a, b| (a - b).cmp(a)` is a comparison function that is neither transitive nor
/// reflexive nor total, `a < b < c < a` with `a = 1, b = 2, c = 3`. For more information and
/// examples see the [`Ord`] documentation.
///
/// All original elements will remain in the slice and any possible modifications via interior
/// mutability are observed in the input. Same is true if `compare` panics.
///
/// # Panics
///
/// May panic if the `compare` does not implement a [total order], or if
/// the `compare` itself panics, or if the specified range is out of bounds.
///
/// # Examples
///
/// ```
/// #![feature(slice_partial_sort_unstable)]
///
/// let mut v = [4, -5, 1, -3, 2];
///
/// // empty range, nothing changed
/// v.partial_sort_unstable_by(0..0, |a, b| b.cmp(a));
/// assert_eq!(v, [4, -5, 1, -3, 2]);
///
/// // single element range, same as select_nth_unstable
/// v.partial_sort_unstable_by(2..3, |a, b| b.cmp(a));
/// assert_eq!(v[2], 1);
///
/// // partial sort a subrange
/// v.partial_sort_unstable_by(1..4, |a, b| b.cmp(a));
/// assert_eq!(&v[1..4], [2, 1, -3]);
///
/// // partial sort the whole range, same as sort_unstable
/// v.partial_sort_unstable_by(.., |a, b| b.cmp(a));
/// assert_eq!(v, [4, 2, 1, -3, -5]);
/// ```
///
/// [total order]: https://en.wikipedia.org/wiki/Total_order
#[unstable(feature = "slice_partial_sort_unstable", issue = "149046")]
#[inline]
pub fn partial_sort_unstable_by<F, R>(&mut self, range: R, mut compare: F)
where
F: FnMut(&T, &T) -> Ordering,
R: RangeBounds<usize>,
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this implementation should be under sort::unstable::partial_sort instead, and like its cousin sort::unstable::sort take a function returning a boolean. Then partial_sort_unstable(_by(_key)) can all dispatch to that one directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then partial_sort_unstable(_by(_key)) can all dispatch to that one directly.

Not quite. I think sort_unstable and sort_unstable_by_key should instead redirect to sort_unstable_by and so does select_nth_unstable series, as what binary_search and partition_dedup do.

The final implement may be factored out to sort::unstable::partial_sort. But I'd prefer to align all these styles in a follow-up patch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I think that would be a mistake, and would reject that follow-up patch. The sorts are incredibly sensitive to inlining and I would highly prefer to minimize the number of steps of indirection to maximize the chance the comparison is inlined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Let me try to find a place for the common partial_sort impls :D

let len = self.len();
let Range { start, end } = slice::range(range, ..len);

if start + 1 > end {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What a very strange way to write start == end. slice::range already disallows inverted ranges.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I'd leave it for now and it's OK to change to start == end if it's more preferred. This is mainly personal preference.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it also costs two extra instructions compared to start == end: https://rust.godbolt.org/z/fe37Y8Pb3.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your information! I'll update it in my next commit then.

// target range is empty, nothing to do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect if we do provide the guarantees for elements before and after the range I mentioned in my comment on the ACP. I think those guarantees are more useful than allowing the partial sort by an empty range in the middle to be a no-op.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Let me try to figure out how to work it out. From what we have now, perhaps we'd select a on a..a for partitioning, and properly handle the case where the slice is empty to avoid panic.

return;
}

let index = start;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think assigning start to a variable which is used only once and is later shadowed with a different definition is not a good idea. Just directly pass start to partition_at_index IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sense.

let (_, _, rest) =
sort::select::partition_at_index(self, index, |a, b| compare(a, b) == Less);

if start + 2 > end {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, very strange way to write end - start == 1. I think this branch is worthwhile though, select_nth_unstable does guarantee that this singular element is in its correct place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite the style issue, I'm considering also what we'd specially handle whenstart == 0 so that a partitioning by end - 1 + sort can eliminate the first select_nth(0). But I'm not sure if the complexity handling more cases worth.

Copy link
Contributor Author

@tisonkun tisonkun Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start == 0 should be the most common case. And a..a would be easier to handle if we distinguish 0..0 (nothing to do) and a..a (a > 0) (must have (a-1)-th element to be partitioned on).

Let me try to apply this idea.

// target range is a single element, nothing more to do
return;
}

let index = end - start - 2;
let (rest, _, _) =
sort::select::partition_at_index(rest, index, |a, b| compare(a, b) == Less);

sort::unstable::sort(rest, &mut |a, b| compare(a, b) == Less);
}

/// Partially sorts the slice in ascending order with a key extraction function, **without**
/// preserving the initial order of equal elements.
///
/// Upon completion, the elements in the specified `range` will be the elements of the slice
/// as if the whole slice were sorted in ascending order.
///
/// This sort is unstable (i.e., may reorder equal elements), in-place (i.e., does not
/// allocate), and *O*(*n* + *k* \* log(*k*)) worst-case.
///
/// If the implementation of [`Ord`] for `K` does not implement a [total order], the function
/// may panic; even if the function exits normally, the resulting order of elements in the slice
/// is unspecified. See also the note on panicking below.
///
/// For example `|a, b| (a - b).cmp(a)` is a comparison function that is neither transitive nor
/// reflexive nor total, `a < b < c < a` with `a = 1, b = 2, c = 3`. For more information and
/// examples see the [`Ord`] documentation.
///
/// All original elements will remain in the slice and any possible modifications via interior
/// mutability are observed in the input. Same is true if the implementation of [`Ord`] for `K` panics.
///
/// # Panics
///
/// May panic if the implementation of [`Ord`] for `K` does not implement a [total order], or if
/// the [`Ord`] implementation panics, or if the specified range is out of bounds.
///
/// # Examples
///
/// ```
/// #![feature(slice_partial_sort_unstable)]
///
/// let mut v = [4i32, -5, 1, -3, 2];
///
/// // empty range, nothing changed
/// v.partial_sort_unstable_by_key(0..0, |k| k.abs());
/// assert_eq!(v, [4, -5, 1, -3, 2]);
///
/// // single element range, same as select_nth_unstable
/// v.partial_sort_unstable_by_key(2..3, |k| k.abs());
/// assert_eq!(v[2], -3);
///
/// // partial sort a subrange
/// v.partial_sort_unstable_by_key(1..4, |k| k.abs());
/// assert_eq!(&v[1..4], [2, -3, 4]);
///
/// // partial sort the whole range, same as sort_unstable
/// v.partial_sort_unstable_by_key(.., |k| k.abs());
/// assert_eq!(v, [1, 2, -3, 4, -5]);
/// ```
///
/// [total order]: https://en.wikipedia.org/wiki/Total_order
#[unstable(feature = "slice_partial_sort_unstable", issue = "149046")]
#[inline]
pub fn partial_sort_unstable_by_key<K, F, R>(&mut self, range: R, mut f: F)
where
F: FnMut(&T) -> K,
K: Ord,
R: RangeBounds<usize>,
{
self.partial_sort_unstable_by(range, |a, b| f(a).cmp(&f(b)));
}

/// Reorders the slice such that the element at `index` is at a sort-order position. All
/// elements before `index` will be `<=` to this value, and all elements after will be `>=` to
/// it.
Expand Down
Loading