From da2401d54aa2d4cab1142fe4b1f90f28d316d4cc Mon Sep 17 00:00:00 2001 From: kageru Date: Thu, 7 Dec 2023 12:49:37 +0100 Subject: [PATCH] add 2023/07/2 --- 2023/src/bin/day05.rs | 8 ++-- 2023/src/bin/day07.rs | 106 +++++++++++++++++++++++++++++++----------- 2 files changed, 82 insertions(+), 32 deletions(-) diff --git a/2023/src/bin/day05.rs b/2023/src/bin/day05.rs index 7f65717..5e46ea8 100644 --- a/2023/src/bin/day05.rs +++ b/2023/src/bin/day05.rs @@ -26,7 +26,7 @@ fn parse_input(raw: &str) -> Parsed { (seeds, ranges) } -fn resolve(start: I, mappings: &Vec) -> I { +fn resolve(start: I, mappings: &[Mapping]) -> I { mappings.iter().fold(start, |i, map| map.iter().find_map(|(range, _, offset)| range.contains(&i).then_some(i + offset)).unwrap_or(i)) } @@ -34,7 +34,7 @@ fn part1((seeds, mappings): &Parsed) -> I { seeds.iter().map(|&s| resolve(s, mappings)).min().unwrap() } -fn resolve_backwards(start: I, mappings: &Vec) -> I { +fn resolve_backwards(start: I, mappings: &[Mapping]) -> I { mappings.iter().fold(start, |i, map| map.iter().find_map(|(_, range, offset)| range.contains(&i).then_some(i - offset)).unwrap_or(i)) } @@ -77,8 +77,8 @@ fn part2((seeds, mappings): &Parsed) -> I { .unwrap() } -fn has_starting_seed(start: I, offset: i64, seed_ranges: &Vec>, mappings: &Vec) -> bool { - let seed = resolve_backwards(start - offset, &mappings); +fn has_starting_seed(start: I, offset: i64, seed_ranges: &[Range], mappings: &[Mapping]) -> bool { + let seed = resolve_backwards(start - offset, mappings); // If seed == s, the entire resolution didn’t hit a single mapping, so we don’t need to check seeds. seed != start && seed_ranges.iter().any(|r| r.contains(&seed)) } diff --git a/2023/src/bin/day07.rs b/2023/src/bin/day07.rs index f0a53ad..da8e6a3 100644 --- a/2023/src/bin/day07.rs +++ b/2023/src/bin/day07.rs @@ -1,55 +1,105 @@ #![feature(test)] extern crate test; + use aoc2023::{boilerplate, common::*}; use itertools::Itertools; const DAY: usize = 7; type I = usize; -type Parsed = Vec<([I; 5], I, I)>; +type Hand = [u8; 5]; +type Parsed = Vec<(Hand, I)>; const CARDS: [u8; 13] = [b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'T', b'J', b'Q', b'K', b'A']; +const CARDS_P2: [u8; 13] = [b'J', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'T', b'Q', b'K', b'A']; + +enum Quality { + AllEqual = 1 << 30, + Quad = 1 << 29, + FullHouse = 1 << 28, + Triple = 1 << 27, + TwoPair = 1 << 26, + Pair = 1 << 25, + None = 1 << 24, +} + +impl Quality { + // How adding 1 joker raises the quality of a hand. + fn upgrade(&self) -> Self { + match self { + Quality::AllEqual | Quality::Quad => Quality::AllEqual, + Quality::Triple | Quality::FullHouse => Quality::Quad, + Quality::TwoPair => Quality::FullHouse, + Quality::Pair => Quality::Triple, + Quality::None => Quality::Pair, + } + } +} fn parse_input(raw: &str) -> Parsed { raw.lines() .map(|l| l.split_once(' ').unwrap()) - .map(|(hand, points)| { - (<[_; 5]>::try_from(hand.as_bytes()).unwrap().map(|b| CARDS.iter().position(|&c| c == b).unwrap()), parse_num(points)) - }) - .map(|(hand, points)| (hand, points, hand.iter().fold(0, |acc, n| (acc << 4) + n))) + .map(|(hand, points)| (<[_; 5]>::try_from(hand.as_bytes()).unwrap(), parse_num(points))) .collect() } +fn rate_hand(mut hand: Hand) -> Quality { + hand.sort(); + let paired = hand + .into_iter() + .map(|c| (c, 1)) + .coalesce(|(c1, n1), (c2, n2)| if c1 == c2 { Ok((c1, n1 + n2)) } else { Err(((c1, n1), (c2, n2))) }) + .map(|(_, n)| n) + .collect_vec(); + match paired.as_slice() { + [_] => Quality::AllEqual, + [4, 1] | [1, 4] => Quality::Quad, + [3, 2] | [2, 3] => Quality::FullHouse, + a @ [_, _, _] if a.contains(&3) => Quality::Triple, + [_, _, _] => Quality::TwoPair, + [_, _, _, _] => Quality::Pair, + _ => Quality::None, + } +} + +fn tiebreaker(hand: &Hand, values: &[u8; 13]) -> usize { + let tiebreak_hand = hand.map(|b| values.iter().position(|&c| c == b).unwrap()); + tiebreak_hand.iter().fold(0, |acc, n| (acc << 4) + n) +} + fn part1(hands: &Parsed) -> usize { let mut rated_hands = hands .iter() .cloned() - .map(|(mut hand, points, tiebreak)| { - hand.sort(); - let paired = hand - .into_iter() - .map(|c| (c, 1)) - .coalesce(|(c1, n1), (c2, n2)| if c1 == c2 { Ok((c1, n1 + n2)) } else { Err(((c1, n1), (c2, n2))) }) - .map(|(_, n)| n) - .collect_vec(); - let value = match paired.as_slice() { - [_] => (1 << 30) + tiebreak, - [4, 1] | [1, 4] => (1 << 29) + tiebreak, - [3, 2] | [2, 3] => (1 << 28) + tiebreak, - [3, 1, 1] | [1, 3, 1] | [1, 1, 3] => (1 << 27) + tiebreak, - // can only be 2 pair - [_, _, _] => (1 << 26) + tiebreak, - [_, _, _, _] => (1 << 25) + tiebreak, - _ => (1 << 24) + tiebreak, - }; - (value, points) + .map(|(hand, points)| { + let tiebreak = tiebreaker(&hand, &CARDS); + (rate_hand(hand) as usize + tiebreak, points) }) .collect_vec(); rated_hands.sort_unstable_by_key(|(v, _)| *v); rated_hands.into_iter().zip(1..).map(|((_, points), position)| points * position).sum() } -fn part2(parsed: &Parsed) -> usize { - unimplemented!() +fn part2(hands: &Parsed) -> usize { + let mut rated_hands = hands + .iter() + .cloned() + .map(|(mut hand, points)| { + let tiebreak = tiebreaker(&hand, &CARDS_P2); + // Count how many jokers there are and insert nonsense data for them so they can’t produce any pairs. + let mut jokers = 0; + for c in hand.iter_mut().filter(|c| c == &&b'J') { + *c = jokers; + jokers += 1; + } + let mut rating = rate_hand(hand); + for _ in 0..jokers { + rating = rating.upgrade(); + } + (rating as usize + tiebreak, points) + }) + .collect_vec(); + rated_hands.sort_unstable_by_key(|(v, _)| *v); + rated_hands.into_iter().zip(1..).map(|((_, points), position)| points * position).sum() } boilerplate! { @@ -61,9 +111,9 @@ KTJJT 220 QQQJA 483", tests: { part1: { TEST_INPUT => 6440 }, - part2: { TEST_INPUT => 0 }, + part2: { TEST_INPUT => 5905 }, }, bench1 == 248812215, - bench2 == 0, + bench2 == 250057090, bench_parse: Vec::len => 1000, }