Skip to content
Merged
Show file tree
Hide file tree
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
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ A [separate changelog is kept for rand_core](https://github.com/rust-random/core

You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful.

## [Unreleased]

### Changes
- Document required output order of fn `partial_shuffle` and apply `#[must_use]` ([#1769])

[#1769]: https://github.com/rust-random/rand/pull/1769

## [0.10.1] — 2026-02-11
This release includes a fix for a soundness bug; see [#1763].

Expand Down Expand Up @@ -1156,7 +1163,7 @@ Code replaced with a compatibility layer over rand 0.4.
### Added
- Separate `rand` out of the standard library

[Unreleased]: https://github.com/rust-random/rand/compare/0.10.0...HEAD
[Unreleased]: https://github.com/rust-random/rand/compare/0.10.1...HEAD
[0.10.1]: https://github.com/rust-random/rand/compare/0.10.0...0.10.1
[0.10.0]: https://github.com/rust-random/rand/compare/0.9.2...0.10.0
[0.9.2]: https://github.com/rust-random/rand/compare/0.9.1...0.9.2
Expand Down
2 changes: 1 addition & 1 deletion benches/benches/shuffle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fn bench_rng<R: Rng + SeedableRng>(c: &mut Criterion, rng_name: &'static str) {
let mut rng = R::seed_from_u64(123);
let mut vec: Vec<usize> = (0..length).collect();
b.iter(|| {
vec.partial_shuffle(&mut rng, length / 2);
let _ = vec.partial_shuffle(&mut rng, length / 2);
vec[0]
})
});
Expand Down
44 changes: 28 additions & 16 deletions src/seq/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,23 +420,35 @@ pub trait SliceRandom: IndexedMutRandom {
where
R: Rng + ?Sized;

/// Shuffle a slice in place, but exit early.
/// Sample `amount` shuffled elements
///
/// Returns two mutable slices from the source slice. The first contains
/// `amount` elements randomly permuted. The second has the remaining
/// elements that are not fully shuffled.
/// Shuffles `amount` random elements into the end of the slice (`n..` where
/// `n = self.len() - amount`). The rest of the slice (`..n`) contains the
/// remaining elements in a permuted but not fully shuffled order.
///
/// Returns a tuple of the sampled elements (`&mut self[n..]`) and the
/// remaining elements (`&mut self[..n]`).
///
/// This is an efficient method to select `amount` elements at random from
/// the slice, provided the slice may be mutated.
///
/// If you only need to choose elements randomly and `amount > self.len()/2`
/// then you may improve performance by taking
/// `amount = self.len() - amount` and using only the second slice.
/// For slices, complexity is `O(m)` where `m = amount`.
/// If `amount >= self.len()` this is equivalent to [`Self::shuffle`].
///
/// If `amount` is greater than the number of elements in the slice, this
/// will perform a full shuffle.
/// # Example
///
/// For slices, complexity is `O(m)` where `m = amount`.
/// ```
/// use rand::seq::SliceRandom;
///
/// let mut rng = rand::rng();
/// let mut y = [1, 2, 3, 4, 5];
/// let (shuffled, rest) = y.partial_shuffle(&mut rng, 3);
/// assert_eq!(shuffled.len(), 3);
/// assert_eq!(rest.len(), 2);
/// let sampled = shuffled.to_vec();
/// assert_eq!(&sampled, &y[2..5]);
/// ```
#[must_use]
fn partial_shuffle<R>(
&mut self,
rng: &mut R,
Expand Down Expand Up @@ -464,14 +476,14 @@ impl<T> SliceRandom for [T] {
// There is no need to shuffle an empty or single element slice
return;
}
self.partial_shuffle(rng, self.len());
let _ = self.partial_shuffle(rng, self.len());
}

fn partial_shuffle<R>(&mut self, rng: &mut R, amount: usize) -> (&mut [T], &mut [T])
where
R: Rng + ?Sized,
{
let m = self.len().saturating_sub(amount);
let n = self.len().saturating_sub(amount);

// The algorithm below is based on Durstenfeld's algorithm for the
// [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm)
Expand All @@ -483,18 +495,18 @@ impl<T> SliceRandom for [T] {
// but only works for 32 bit integers
// So we must use the slow method if the slice is longer than that.
if self.len() < (u32::MAX as usize) {
let mut chooser = IncreasingUniform::new(rng, m as u32);
for i in m..self.len() {
let mut chooser = IncreasingUniform::new(rng, n as u32);
for i in n..self.len() {
let index = chooser.next_index();
self.swap(i, index);
}
} else {
for i in m..self.len() {
for i in n..self.len() {
let index = rng.random_range(..i + 1);
self.swap(i, index);
}
}
let r = self.split_at_mut(m);
let r = self.split_at_mut(n);
(r.1, r.0)
}
}
Expand Down