Skip to main content

aoc/year2023/
day07.rs

1//! # Camel Cards
2//!
3//! The type of each hand is computed from the frequency of its cards in descending order.
4//! For example, a full house has 1 card with a frequency of 3 and a second with a frequency of 2,
5//! giving `[3, 2]`. Similarly, two pair is `[2, 2, 1]`. To make comparisons faster the frequencies
6//! and the card ranks are packed into a `usize`, for example:
7//!
8//! * `55222` => `0x3200055222`
9//! * `32T3K` => `0x2111032a3d`
10//!
11//! For part two, the strongest hand type is always made by adding the number of jokers to the
12//! highest frequency card (which could also be jokers in the case of `JJJJJ`).
13//!
14//! * `QQQJA` => `0x41000ccc1a`
15use std::cmp::Reverse;
16use std::mem::replace;
17
18use crate::util::parse::*;
19
20pub struct Hand {
21    cards: [u8; 5],
22    bid: usize,
23}
24
25pub fn parse(input: &str) -> Vec<Hand> {
26    input
27        .lines()
28        .map(|line| {
29            let (prefix, suffix) = line.split_at(5);
30            let cards = prefix.as_bytes().try_into().unwrap();
31            let bid = suffix.unsigned();
32            Hand { cards, bid }
33        })
34        .collect()
35}
36
37pub fn part1(input: &[Hand]) -> usize {
38    winnings(input, 11)
39}
40
41pub fn part2(input: &[Hand]) -> usize {
42    winnings(input, 1)
43}
44
45fn winnings(input: &[Hand], jack: usize) -> usize {
46    let mut hands: Vec<_> = input
47        .iter()
48        .map(|&Hand { cards, bid }| {
49            let ranks = cards.map(|b| match b {
50                b'A' => 14,
51                b'K' => 13,
52                b'Q' => 12,
53                b'J' => jack,
54                b'T' => 10,
55                _ => b.to_decimal(),
56            });
57
58            let mut frequency = [0; 15];
59            for rank in ranks {
60                frequency[rank] += 1;
61            }
62
63            // Set jokers aside so that they increase the biggest group.
64            let jokers = replace(&mut frequency[1], 0);
65
66            // Each card contributes its frequency once, then zero for any duplicates.
67            let mut groups = ranks.map(|rank| replace(&mut frequency[rank], 0));
68            groups.sort_unstable_by_key(|&count| Reverse(count));
69            groups[0] += jokers;
70
71            // To speed up comparisons, pack the groups and card ranks into hex nibbles.
72            let key = groups.iter().chain(&ranks).fold(0, |key, &value| (key << 4) | value);
73            (key, bid)
74        })
75        .collect();
76
77    hands.sort_unstable();
78    hands.into_iter().zip(1..).map(|((_, bid), rank)| bid * rank).sum()
79}