Search code examples
randomrustshuffle

Better random shuffler in Rust?


I just made a program that creates a deck with 26 cards, splits it into 5 hands of 5 cards (discarding 1) and checks the poker combinations that are in those hands, if any.

Now, I also made another program that loops over this until a Royal Flush is found (which is very rare and should happen, on average, once every 600k or so decks).

And the strange thing is, once I added a counter to show how many decks it went through, it only said 150 - 4000 ! And I think it's the random shuffler's fault. I had made a similar program in Python, and that was checking approximately the correct amount of decks.

I used this, which should shuffle the deck in place:

fn shuffle_deck(deck: &mut Vec<Card>) -> () {
    deck.shuffle(&mut rand::thread_rng())
}

Apparently, it's not doing a very good job at being random. Can anybody help me in finding a better solution?

Edit: also, for anyone wondering, this is the Card struct:

pub struct Card {
    value: i32,
    suit: String
}

Solution

  • Your assumption is incorrect.

    26 cards can make 26 over 5 possible combinations, which is 65780 combinations.

    As you have 4 royal flushes in your deck, the probability to get dealt a royal flush is 4/65780 = 0.006080875646093037 % or one out of ever 16445.

    If you look at four at a time, this number roughly divides by 4 (not exactly because those four draws aren't independent), so you should get something in the ballpark of one every ~3300.

    So if you measure it experimentally, you get:

    use rand::seq::SliceRandom;
    
    fn is_royal_flush(deck: &[u8]) -> bool {
        if deck.len() != 5 {
            false
        } else {
            let mut buckets = [0u8; 4];
            for el in deck {
                if let Some(bucket) = buckets.get_mut((*el / 5) as usize) {
                    *bucket += 1;
                }
            }
    
            buckets.iter().any(|bucket| *bucket == 5)
        }
    }
    
    fn has_royal_flush(deck: &[u8; 26]) -> bool {
        is_royal_flush(&deck[0..5])
            || is_royal_flush(&deck[5..10])
            || is_royal_flush(&deck[10..15])
            || is_royal_flush(&deck[15..20])
            || is_royal_flush(&deck[20..25])
    }
    
    fn main() {
        let mut rng = rand::thread_rng();
    
        let mut deck = [0; 26];
        deck.iter_mut()
            .enumerate()
            .for_each(|(pos, val)| *val = pos as u8);
    
        println!("Deck, initially: {:?}", deck);
        deck.shuffle(&mut rng);
        println!("Deck, shuffled:  {:?}", deck);
        println!();
    
        let mut total: usize = 0;
        let mut royal_flushes: usize = 0;
    
        loop {
            deck.shuffle(&mut rng);
    
            total += 1;
    
            if has_royal_flush(&deck) {
                royal_flushes += 1;
            }
    
            if total % 10000000 == 0 {
                println!(
                    "Now {} out of {}. (Probability: 1/{})",
                    royal_flushes,
                    total,
                    total / royal_flushes
                );
            }
        }
    }
    
    Deck, initially: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
    Deck, shuffled:  [25, 16, 3, 12, 18, 1, 14, 8, 17, 0, 5, 15, 6, 11, 4, 21, 2, 13, 24, 20, 22, 7, 10, 9, 19, 23]
    
    Now 2976 out of 10000000. (Probability: 1/3360)
    Now 6004 out of 20000000. (Probability: 1/3331)
    Now 8973 out of 30000000. (Probability: 1/3343)
    Now 11984 out of 40000000. (Probability: 1/3337)
    

    Which is roughly in the expected ballpark. So your assumption is off that you should get only one royal flush every 600k, and most likely something is wrong with your python code.


    That said, if you compute the probability to get a single royal flush in a 52 card deck, then you should get (52 over 5) / 4 = one every 649740 draws. Which is probably what you were referring to, and if you program it, it matches its expectations:

    use rand::seq::SliceRandom;
    
    fn is_royal_flush(deck: &[u8]) -> bool {
        if deck.len() != 5 {
            false
        } else {
            let mut buckets = [0u8; 4];
            for el in deck {
                if let Some(bucket) = buckets.get_mut((*el / 5) as usize) {
                    *bucket += 1;
                }
            }
    
            buckets.iter().any(|bucket| *bucket == 5)
        }
    }
    
    fn has_royal_flush(deck: &[u8; 52]) -> bool {
        is_royal_flush(&deck[0..5])
    }
    
    fn main() {
        let mut rng = rand::thread_rng();
    
        let mut deck = [0; 52];
        deck.iter_mut()
            .enumerate()
            .for_each(|(pos, val)| *val = pos as u8);
    
        println!("Deck, initially: {:?}", deck);
        deck.shuffle(&mut rng);
        println!("Deck, shuffled:  {:?}", deck);
        println!();
    
        let mut total: usize = 0;
        let mut royal_flushes: usize = 0;
    
        loop {
            deck.shuffle(&mut rng);
    
            total += 1;
    
            if has_royal_flush(&deck) {
                royal_flushes += 1;
            }
    
            if total % 10000000 == 0 {
                println!(
                    "Now {} out of {}. (Probability: 1/{})",
                    royal_flushes,
                    total,
                    total / royal_flushes
                );
            }
        }
    }
    
    Deck, initially: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
    Deck, shuffled:  [2, 15, 44, 39, 26, 47, 28, 11, 1, 19, 31, 6, 43, 42, 29, 48, 35, 30, 3, 49, 50, 37, 9, 10, 18, 45, 33, 22, 36, 5, 38, 46, 51, 32, 7, 17, 23, 27, 41, 14, 21, 13, 25, 4, 8, 16, 20, 24, 12, 34, 0, 40]
    
    Now 6 out of 10000000. (Probability: 1/1666666)
    Now 22 out of 20000000. (Probability: 1/909090)
    Now 36 out of 30000000. (Probability: 1/833333)
    Now 54 out of 40000000. (Probability: 1/740740)
    Now 70 out of 50000000. (Probability: 1/714285)
    Now 78 out of 60000000. (Probability: 1/769230)
    Now 92 out of 70000000. (Probability: 1/760869)
    Now 102 out of 80000000. (Probability: 1/784313)
    Now 125 out of 90000000. (Probability: 1/720000)
    Now 139 out of 100000000. (Probability: 1/719424)
    Now 164 out of 110000000. (Probability: 1/670731)
    Now 190 out of 120000000. (Probability: 1/631578)
    Now 202 out of 130000000. (Probability: 1/643564)
    Now 217 out of 140000000. (Probability: 1/645161)
    Now 232 out of 150000000. (Probability: 1/646551)
    Now 248 out of 160000000. (Probability: 1/645161)