What is the difference between rand::seq::SliceRandom::shuffle and partial_shuffle for randomized permutation?
shuffle randomizes the entire slice in-place, producing a uniformly random permutation of all elements, while partial_shuffle randomizes only the first amount elements, leaving the rest in an unspecified but deterministic order after those positions. The key difference is that partial_shuffle efficiently samples without replacement—you get exactly amount randomly selected elements at the front of the slice, which is useful when you need a random subset rather than a complete permutation.
Basic Shuffle Usage
use rand::seq::SliceRandom;
use rand::thread_rng;
fn basic_shuffle() {
let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// shuffle randomizes the entire slice
data.shuffle(&mut thread_rng());
// All elements are now in random order
// Example output: [7, 2, 10, 1, 5, 9, 3, 8, 4, 6]
println!("{:?}", data);
// Every element appears exactly once, just reordered
// The permutation is uniformly random
}
fn basic_partial_shuffle() {
let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// partial_shuffle randomizes only the first 'amount' elements
let amount = 3;
data.partial_shuffle(&mut thread_rng(), amount);
// First 3 elements are randomly selected from all elements
// Remaining 7 elements are in unspecified order
// Example output: [9, 3, 7, 4, 5, 6, 1, 2, 8, 10]
// ^^^^^^^ these are uniformly random
println!("{:?}", data);
}shuffle touches every element; partial_shuffle only needs to touch amount elements.
Algorithm Comparison
use rand::seq::SliceRandom;
use rand::thread_rng;
fn algorithm_explanation() {
// shuffle implements Fisher-Yates algorithm:
// for i from 0 to n-1:
// j = random index from i to n-1
// swap elements at i and j
//
// This produces a uniformly random permutation
// Time complexity: O(n) - touches every element
// partial_shuffle is Fisher-Yates but stops early:
// for i from 0 to amount-1:
// j = random index from i to n-1
// swap elements at i and j
//
// This produces random elements at positions 0..amount
// Time complexity: O(amount) - touches only 'amount' elements
let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Full shuffle: 10 iterations, 10 random numbers
data.shuffle(&mut thread_rng());
let mut data2 = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Partial shuffle: 3 iterations, 3 random numbers
data2.partial_shuffle(&mut thread_rng(), 3);
}Both use Fisher-Yates; partial_shuffle just stops after amount iterations.
Efficient Random Sampling
use rand::seq::SliceRandom;
use rand::thread_rng;
fn random_subset() {
let population = vec!["Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace"];
// You want 3 random people from 7, without duplicates
// Approach 1: Full shuffle, take first 3
let mut shuffled = population.clone();
shuffled.shuffle(&mut thread_rng());
let sample1: Vec<_> = shuffled.iter().take(3).collect();
// This works but shuffles ALL 7 elements unnecessarily
// Approach 2: partial_shuffle, take first 3
let mut partial = population.clone();
partial.partial_shuffle(&mut thread_rng(), 3);
let sample2: Vec<_> = partial.iter().take(3).collect();
// This is more efficient - only shuffles 3 elements
// For n=7, amount=3:
// Full shuffle: 7 iterations
// Partial shuffle: 3 iterations
// Difference grows with slice size
}
fn efficient_sampling_large_dataset() {
// Imagine a dataset of 1 million items
let data: Vec<u32> = (0..1_000_000).collect();
// You want 10 random samples
// Full shuffle: O(1,000,000)
// Partial shuffle: O(10)
// partial_shuffle is 100,000x more efficient!
let mut data = data;
data.partial_shuffle(&mut thread_rng(), 10);
// First 10 elements are your random sample
let sample: Vec<_> = data.iter().take(10).collect();
}When you need a subset, partial_shuffle is dramatically more efficient for large collections.
Return Value and Ownership
use rand::seq::SliceRandom;
use rand::thread_rng;
fn return_value() {
let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// shuffle returns ()
let result = data.shuffle(&mut thread_rng());
// result is ()
// partial_shuffle returns &mut [T] - the unshuffled tail
let tail = data.partial_shuffle(&mut thread_rng(), 3);
// tail is a mutable reference to elements after the first 3
// This is useful because:
// 1. You get the shuffled portion (first 3)
// 2. You get a reference to the remaining portion
// Example:
let mut cards = vec!["A", "B", "C", "D", "E"];
let remaining = cards.partial_shuffle(&mut thread_rng(), 2);
// cards[..2] is your shuffled sample
// remaining (cards[2..]) is the rest
// You can continue working with remaining
remaining.shuffle(&mut thread_rng()); // Shuffle the remainder
}partial_shuffle returns a mutable reference to the unshuffled portion.
Sampling Without Replacement
use rand::seq::SliceRandom;
use rand::thread_rng;
fn sampling_without_replacement() {
let mut items = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// partial_shuffle gives you exactly 'amount' distinct random elements
// No duplicates possible (unlike random selection with replacement)
items.partial_shuffle(&mut thread_rng(), 3);
// The first 3 elements are guaranteed to be:
// 1. Distinct (no duplicates)
// 2. Randomly selected from all elements
// 3. Uniformly distributed
// This is "sampling without replacement"
let sample: Vec<_> = items.drain(..3).collect();
// Compare to sampling WITH replacement:
fn sample_with_replacement(items: &[i32], count: usize) -> Vec<i32> {
(0..count)
.map(|_| items[thread_rng().gen_range(0..items.len())])
.collect()
}
// This could give duplicates like [5, 5, 5]
}
use rand::Rng;
fn sampling_comparison() {
let data = vec![1, 2, 3, 4, 5];
// Without replacement (partial_shuffle)
let mut without_replace = data.clone();
without_replace.partial_shuffle(&mut thread_rng(), 3);
let sample: Vec<_> = without_replace.iter().take(3).copied().collect();
// sample could be [3, 1, 5] - always 3 distinct elements
// With replacement (random indexing)
let sample_with_replace: Vec<_> = (0..3)
.map(|_| data[thread_rng().gen_range(0..data.len())])
.collect();
// Could be [2, 2, 2] - duplicates allowed
}partial_shuffle guarantees distinct elements when sampling.
Multiple Samples in Sequence
use rand::seq::SliceRandom;
use rand::thread_rng;
fn sequential_sampling() {
let mut items = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Take 3 random elements, do something with them
items.partial_shuffle(&mut thread_rng(), 3);
let first_sample: Vec<_> = items.drain(..3).collect();
println!("First sample: {:?}", first_sample);
// Now items has 7 elements left
// Take another 2 random elements from remaining
items.partial_shuffle(&mut thread_rng(), 2);
let second_sample: Vec<_> = items.drain(..2).collect();
println!("Second sample: {:?}", second_sample);
// This pattern is useful for:
// - Drawing cards from a deck
// - Selecting teams sequentially
// - Processing items in random order
}
fn dealing_cards() {
// Simulate dealing cards from a deck
let mut deck: Vec<&str> = &[
"A♠", "2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠",
"A♥", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "J♥", "Q♥", "K♥",
// ... more suits
][..].to_vec();
// Deal 5 cards to player 1
deck.partial_shuffle(&mut thread_rng(), 5);
let player1: Vec<_> = deck.drain(..5).collect();
// Deal 5 cards to player 2 from remaining deck
deck.partial_shuffle(&mut thread_rng(), 5);
let player2: Vec<_> = deck.drain(..5).collect();
// Players have distinct, random hands
}You can chain partial_shuffle with drain to sample sequentially.
Comparison with Other Random Methods
use rand::seq::{SliceRandom, IteratorRandom, index};
use rand::thread_rng;
fn comparison_with_other_methods() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Method 1: shuffle + take
let mut shuffled = data.clone();
shuffled.shuffle(&mut thread_rng());
let sample1: Vec<_> = shuffled.iter().take(3).copied().collect();
// Complexity: O(n), allocates
// Good for: full permutation needed anyway
// Method 2: partial_shuffle + take
let mut partial = data.clone();
partial.partial_shuffle(&mut thread_rng(), 3);
let sample2: Vec<_> = partial.iter().take(3).copied().collect();
// Complexity: O(amount), allocates
// Good for: sampling small subset from large collection
// Method 3: choose_multiple (from IteratorRandom)
let sample3: Vec<_> = data.iter().choose_multiple(&mut thread_rng(), 3);
// Complexity: O(n) but no mutation needed
// Good for: don't want to mutate, need random subset
// Returns Vec<&T>, not affecting original
// Method 4: random indices
let indices: Vec<_> = (0..3)
.map(|_| index::sample(&mut thread_rng(), data.len(), 1)[0])
.collect();
// Could have duplicates! Not sampling without replacement.
}
use rand::seq::index;
fn choose_multiple_vs_partial_shuffle() {
let large_data: Vec<u32> = (0..1_000_000).collect();
// choose_multiple: O(n) time, O(amount) space
// Must iterate through entire collection
let sample1: Vec<_> = large_data.iter().choose_multiple(&mut thread_rng(), 10);
// partial_shuffle: O(amount) time, in-place
// Only touches 'amount' elements
let mut large_data = large_data;
large_data.partial_shuffle(&mut thread_rng(), 10);
let sample2: Vec<_> = large_data.iter().take(10).copied().collect();
// For large data and small samples:
// partial_shuffle is faster (10 iterations vs 1,000,000)
// choose_multiple is cleaner (no mutation)
}Choose based on whether you need in-place mutation and relative sizes.
Edge Cases and Behavior
use rand::seq::SliceRandom;
use rand::thread_rng;
fn edge_cases() {
// Empty slice
let mut empty: Vec<i32> = vec![];
empty.shuffle(&mut thread_rng()); // No-op, no error
empty.partial_shuffle(&mut thread_rng(), 3); // No-op, no error
// Amount > len
let mut small = vec![1, 2, 3];
small.partial_shuffle(&mut thread_rng(), 10);
// Behaves like full shuffle - all elements randomized
// Effectively min(amount, len) elements are shuffled
// Amount = 0
let mut data = vec![1, 2, 3, 4, 5];
data.partial_shuffle(&mut thread_rng(), 0);
// No elements shuffled, slice unchanged
// Amount = 1
data.partial_shuffle(&mut thread_rng(), 1);
// First element is randomly selected from all elements
// Equivalent to: swap first element with random element
// Amount = len
data.partial_shuffle(&mut thread_rng(), data.len());
// Same as full shuffle
// Single element
let mut single = vec![42];
single.shuffle(&mut thread_rng()); // Single element stays put
single.partial_shuffle(&mut thread_rng(), 1); // Same
}
fn amount_greater_than_length() {
let mut data = vec![1, 2, 3];
// Requesting 10 random elements from 3-element slice
data.partial_shuffle(&mut thread_rng(), 10);
// Result: all 3 elements shuffled (as if amount = 3)
// The implementation uses min(amount, len)
println!("{:?}", data); // Random permutation of [1, 2, 3]
}partial_shuffle safely handles edge cases including amounts larger than slice length.
Performance Characteristics
use rand::seq::SliceRandom;
use rand::thread_rng;
fn performance_comparison() {
// For a slice of length n and sample size k:
// shuffle: O(n) time, O(1) space
// - Always touches every element
// - In-place mutation
// partial_shuffle: O(k) time, O(1) space
// - Touches only k elements
// - In-place mutation
// - Returns &mut to remaining n-k elements
// When to use shuffle:
// - Need all elements randomized
// - k ≈ n (sampling most of the data)
// When to use partial_shuffle:
// - Need only k << n elements (small subset)
// - Sampling without replacement
// - k is much smaller than n
// Example: 1 million items, need 10 random
// shuffle: 1,000,000 iterations
// partial_shuffle: 10 iterations
// 100,000x speedup!
// Example: 10 items, need 9 random
// shuffle: 10 iterations
// partial_shuffle: 9 iterations
// Negligible difference
}
fn benchmark_heuristic() {
// Heuristic: Use partial_shuffle when amount < 0.5 * len
fn should_use_partial(len: usize, amount: usize) -> bool {
amount < len / 2
}
// Use shuffle when amount >= 0.5 * len (simpler, same performance)
fn should_use_shuffle(len: usize, amount: usize) -> bool {
amount >= len / 2
}
// But if you ALWAYS need full randomization, use shuffle for clarity
}Choose based on the ratio of sample size to collection size.
Practical Use Cases
use rand::seq::SliceRandom;
use rand::thread_rng;
fn card_game_deck() {
// Standard deck of cards
let mut deck: Vec<&str> = &[
"A♠", "K♠", "Q♠", "J♠", "10♠", "9♠", "8♠", "7♠", "6♠", "5♠", "4♠", "3♠", "2♠",
"A♥", "K♥", "Q♥", "J♥", "10♥", "9♥", "8♥", "7♥", "6♥", "5♥", "4♥", "3♥", "2♥",
"A♦", "K♦", "Q♦", "J♦", "10♦", "9♦", "8♦", "7♦", "6♦", "5♦", "4♦", "3♦", "2♦",
"A♣", "K♣", "Q♣", "J♣", "10♣", "9♣", "8♣", "7♣", "6♣", "5♣", "4♣", "3♣", "2♣",
][..].to_vec();
// For dealing, use partial_shuffle
// Deal to 4 players, 5 cards each
// Deal to player 1
deck.partial_shuffle(&mut thread_rng(), 5);
let player1: Vec<_> = deck.drain(..5).collect();
// Deal to player 2
deck.partial_shuffle(&mut thread_rng(), 5);
let player2: Vec<_> = deck.drain(..5).collect();
// Deal to player 3
deck.partial_shuffle(&mut thread_rng(), 5);
let player3: Vec<_> = deck.drain(..5).collect();
// Deal to player 4
deck.partial_shuffle(&mut thread_rng(), 5);
let player4: Vec<_> = deck.drain(..5).collect();
// Each player has 5 random, distinct cards
}
fn lottery_selection() {
// 1000 participants, select 10 winners
let mut participants: Vec<u32> = (1..=1000).collect();
// Use partial_shuffle for efficiency
participants.partial_shuffle(&mut thread_rng(), 10);
// First 10 are winners (in random order)
let winners: Vec<_> = participants.iter().take(10).copied().collect();
println!("Winners: {:?}", winners);
// Only touched 10 elements, not 1000
}
fn playlist_shuffle() {
let mut playlist = vec![
"song1.mp3", "song2.mp3", "song3.mp3", "song4.mp3", "song5.mp3",
"song6.mp3", "song7.mp3", "song8.mp3", "song9.mp3", "song10.mp3",
];
// If playing entire playlist, use shuffle
playlist.shuffle(&mut thread_rng());
// If previewing first few songs, use partial_shuffle
let mut playlist2 = playlist.clone();
playlist2.partial_shuffle(&mut thread_rng(), 3);
let preview: Vec<_> = playlist2.iter().take(3).collect();
}Real-world applications include card games, random sampling, and playlist shuffling.
Working with the Tail
use rand::seq::SliceRandom;
use rand::thread_rng;
fn using_tail() {
let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// partial_shuffle returns the unshuffled portion
let tail = data.partial_shuffle(&mut thread_rng(), 3);
// data[..3] contains 3 random elements
// tail contains the remaining elements in unspecified order
// You can continue working with the tail:
tail.shuffle(&mut thread_rng()); // Shuffle remaining elements
println!("{:?}", data);
// First 3: random sample
// Last 7: random permutation of remainder
// Or partition the slice differently
}
fn two_phase_randomization() {
let mut items = vec!["a", "b", "c", "d", "e", "f", "g", "h"];
// Phase 1: Randomize first 3 positions
let remainder = items.partial_shuffle(&mut thread_rng(), 3);
// First 3 are random selection
let priority: Vec<_> = items[..3].to_vec();
// Phase 2: Shuffle the remainder differently
remainder.shuffle(&mut thread_rng());
// Now items is partitioned:
// items[0..3]: 3 random elements
// items[3..]: Remaining elements, also shuffled
println!("Priority: {:?}", priority);
println!("All: {:?}", items);
}The returned tail reference enables chaining operations.
Summary Table
fn summary() {
// | Aspect | shuffle | partial_shuffle |
// |-----------------------|----------------------------|--------------------------------|
// | Elements affected | All (n) | First amount only |
// | Time complexity | O(n) | O(amount) |
// | Space complexity | O(1) in-place | O(1) in-place |
// | Return value | () | &mut [T] (tail) |
// | Use case | Full permutation | Random subset |
// | Guarantees | Uniform random order | First k are uniform random |
// | When amount > len | N/A (always n) | Behaves like shuffle |
// | When amount = 0 | N/A | No change |
// | When amount = len | Same as shuffle | Same as shuffle |
// Quick decision guide:
// - Need full permutation? Use shuffle
// - Need random subset? Use partial_shuffle
// - Need to draw sequentially? Use partial_shuffle + drain
// - Need immutable sampling? Use choose_multiple
}Synthesis
Quick reference:
use rand::seq::SliceRandom;
use rand::thread_rng;
// Full shuffle: all elements randomized
let mut data = vec![1, 2, 3, 4, 5];
data.shuffle(&mut thread_rng());
// data is now a random permutation
// Partial shuffle: only first k elements randomized
let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
data.partial_shuffle(&mut thread_rng(), 3);
// data[..3] contains 3 random elements from all 10
// data[3..] contains remaining elements in unspecified orderKey insight: Both shuffle and partial_shuffle implement the Fisher-Yates algorithm, differing only in how many iterations they perform. shuffle runs the full algorithm, producing a uniformly random permutation of all elements—essential when you need the entire collection randomized, like shuffling a deck of cards before dealing the whole hand. partial_shuffle stops after amount iterations, leaving the first amount positions with uniformly random elements from the original slice. This is dramatically more efficient when you need only a subset: for a million-element collection where you need 10 random samples, shuffle touches all million elements while partial_shuffle touches only 10. The return value of partial_shuffle—a mutable reference to the tail—is useful for chaining: you can partial_shuffle to draw some elements, drain them out, then continue with the remainder. The tail itself has unspecified order (the algorithm touched it, but positions after amount weren't explicitly randomized), so if you need the remaining portion truly random, you should shuffle it explicitly. For pure sampling without modifying the original collection, IteratorRandom::choose_multiple is an alternative, but it iterates through all elements rather than stopping early, making partial_shuffle the better choice for small samples from large collections.
