Rust – Sometimes you don’t need to use HashMap or HashSet
I 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;
}
}