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 order

Key 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.