Rust – Sometimes you don’t need to use HashMap or HashSet

Seven playing cardsI wrote a Poker Hand evaluator. It read in a file of a 1,000 randomly generated sets of seven cards like this AC 8H JC …and figured out what was the best hand. It’s in Rust and on GitHub. That takes you to a projects folder and the whole rust project complete with two test sets of cards are included. Look for the file Rust_pokerhand.zip.

Now in the Hand evaluation I used both a HashMap and HashSet.  Here for example is the code to count how many of each Rank there are.

    let mut rank_counts = HashMap::new();
    for card in cards.iter() {
        *rank_counts.entry(card.rank).or_insert(0) += 1;
    }

The program reads in the cards into a Vec<Vec<Card>> and then calculate the average time to evaluate a hand.

Now there’s nothing wrong with the program. One of the card sets is called test_card_hands.txt and has an instance of each hand type along with a comment.

5H AS 2D 6C 3S KD 7C - High card
7H 3D 8S 7C 4H 9H JC - One Pair
AD 7H 8C AC 5S 8D KS - Two Pairs

You can run that with this command:

cargo run --release --features "show_cards" test_card_hands.txt

Or if you run the other test set with this, it doesn’t show the cards but does calculate the average time.

cargo run --release 1000_card_hands.txt

If I run this on Windows it evaluates a hand in 780 ns on average and on Ubuntu 24.04 on the same PC, (running under Hyper-V), it’s faster typically 550 ns.

So, I was looking for ways to speed it up and thought.  The HasmMap and HashSets are running on small numbers of Cards. 13 for Ranks and 4 for suits so what if I use them as arrays. Would it be faster?

It was, roughly four times faster.

As an example, the code above I replaced with this:

    let mut rank_counts = [0,0,0,0,0,0,0,0,0,0,0,0,0];
    for card in cards.iter() {
       rank_counts[&card.rank.value()-2] += 1;
    }

That needed a value() method that mapped all the Ranks starting at 2, onto 2-14.

I also redid the code for working out a straight.

The original is this, plus a bit more to check for A2345:

    let mut values: HashSet<u8> = cards.iter().map(|card| card.rank.value()).collect();
    let mut unique_values: Vec<u8> = values.drain().collect();
    unique_values.sort_unstable();

    let mut straight_length = 1;
    for i in 1..unique_values.len() {
        if unique_values[i] == unique_values[i - 1] + 1 {
            straight_length += 1;
            if straight_length == 5 {
                is_straight = true;
                break;
            }
        } else {
            straight_length = 1;
        }
    }
The improved version uses this constant

static STRAIGHTS: &'static [i32]=&[7936, 3968, 1984, 992, 496, 248, 124, 62, 31, 7681];

Plus a binvalue function which maps the card Ranks onto 1,2,4,8 etc. Just or the Cards binValues together and ‘ands’ (&) it with the individual STRAIGHTS values.

      let mut bivalue=0;
      for card in cards.iter() {
        bivalue |= card.rank.binvalue();
      }   

      for i in 0..STRAIGHTS.len(){
        if &binvalue & STRAIGHTS[i]== STRAIGHTS[i] {
            is_straight= true;
            break;
        }
    }
(Visited 40 times, 1 visits today)