Skip to content

Instantly share code, notes, and snippets.

@koivunej
Last active December 3, 2023 08:02
Show Gist options
  • Save koivunej/880d011674d20e613fc906aaa9f9f64a to your computer and use it in GitHub Desktop.
Save koivunej/880d011674d20e613fc906aaa9f9f64a to your computer and use it in GitHub Desktop.
aoc 2023 day 03, buffering hashmappy
use std::collections::HashMap;
fn main() {
const INPUT: &str = include_str!("../input");
let (parts, ratios) = find_parts(INPUT);
let part1 = parts.iter().sum::<u64>();
println!("{part1}");
let part2 = ratios.iter().sum::<u64>();
println!("{part2}");
assert_eq!(part1, 537_832);
assert_eq!(part2, 81_939_900);
}
#[test]
fn example() {
let s = r#"467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598.."#;
let (part_numbers, gear_ratios) = find_parts(s);
assert_eq!(4361, part_numbers.iter().sum::<u64>());
assert_eq!(467835, gear_ratios.iter().sum::<u64>());
}
fn find_parts(s: &str) -> (Vec<u64>, Vec<u64>) {
// collect the symbol positions, unique parts and cells used by parts
//
// unique parts are defensive against same number appearing in different parts of the map.
//
// cells used by the part is to make up later scanning easier (only need to check 8 cells
// around a symbol).
let (symbols, mut parts_by_id, mut part_coordinates) = {
let mut parsing = String::with_capacity(8);
let mut parsing_from = None;
let mut part_id_counter = 1;
let mut parts_by_id = HashMap::new();
let mut part_coordinates = HashMap::new();
let mut symbols = Vec::new();
let mut pos = (0u32, 0u32);
for ch in s.chars() {
pos.0 += 1;
let (flush_parsed, symbol) = match ch {
'\n' => (true, false),
'.' => (true, false),
'0'..='9' => {
if parsing_from.is_none() {
parsing_from = Some(pos);
}
parsing.push(ch);
(false, false)
}
_ => (true, true),
};
if flush_parsed {
if let Some(origin) = parsing_from.take() {
let num = parsing.parse::<u64>().unwrap();
let id = part_id_counter;
part_id_counter += 1;
// unique number which we'll only consume once
let old = parts_by_id.insert(id, num);
assert!(old.is_none());
let y = pos.1;
for x in origin.0..pos.0 {
// record a link to this unique number for all cells it covered
let old = part_coordinates.insert((x, y), id);
assert!(old.is_none());
}
parsing.clear();
}
}
if symbol {
symbols.push((ch, pos));
}
if ch == '\n' {
pos = (0, pos.1 + 1);
}
}
(symbols, parts_by_id, part_coordinates)
};
let mut actual_parts = Vec::new();
let mut gear_ratios = Vec::new();
let mut scratch = Vec::new();
// the input must be crafted so that there is no competition on which symbol "pulls" in which
// number, or perhaps this matters only that there is no competition with '*' for phase2.
for (ch, pos) in symbols {
scratch.clear();
scratch.extend(
adjacent_of(pos)
.filter_map(|pos| part_coordinates.remove(&pos))
.filter_map(|id| parts_by_id.remove(&id))
.inspect(|num| println!("symbol {ch} at {pos:?} found {num}")),
);
if scratch.len() == 2 && ch == '*' {
gear_ratios.push(scratch.iter().product::<u64>());
}
actual_parts.append(&mut scratch);
}
// println!("actual_parts: {:?}", actual_parts);
// println!("leftover: {:?}", parts_by_id.values());
// println!("gear ratios: {:?}", gear_ratios);
(actual_parts, gear_ratios)
}
#[rustfmt::skip]
const fn adjacent_offsets() -> [(i32, i32); 8] {
[(-1, -1), ( 0, -1), ( 1, -1),
(-1, 0), ( 1, 0),
(-1, 1), ( 0, 1), ( 1, 1)]
}
fn adjacent_of(pos: (u32, u32)) -> impl Iterator<Item = (u32, u32)> {
adjacent_offsets().into_iter().filter_map(move |p| {
Some((
pos.0.checked_add_signed(p.0)?,
pos.1.checked_add_signed(p.1)?,
))
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment