Up to Day 21 (excluding day 16)

This commit is contained in:
Christian
2023-12-21 17:37:32 +01:00
parent 6ceb565e34
commit 9594c29467
34 changed files with 8103 additions and 1 deletions

View File

@@ -69,7 +69,7 @@ impl Entry {
fn main() -> Result<()> {
let filename = env::args()
.nth(1)
.context("./day03 <path to puzzle input>")?;
.context("./day05 <path to puzzle input>")?;
let input = fs::read_to_string(filename)?;
let mut sections = input.split("\n\n");

39
src/bin/day06.rs Normal file
View File

@@ -0,0 +1,39 @@
use anyhow::{Context, Result};
use itertools::Itertools;
fn win_bounds(time: u64, record: u64) -> (u64, u64) {
let det_sq = time * time - 4 * record;
let det = (det_sq as f64).sqrt();
let mut low = ((time as f64 - det) / 2.0).ceil() as u64;
low += ((time - low) * low <= record) as u64;
let mut high = ((time as f64 + det) / 2.0).floor() as u64;
high -= ((time - low) * low <= record) as u64;
(low, high)
}
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day06 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let mut nums = input.lines().map(|line| {
line.split_ascii_whitespace()
.skip(1)
.map(|d| d.parse().unwrap())
.collect::<Vec<_>>()
});
let (times, records) = nums.next_tuple().unwrap();
let races = times.iter().zip(records.iter());
let range = |(low, high)| high - low + 1;
let part1: u64 = races.map(|(&t, &r)| range(win_bounds(t, r))).product();
let concat = |v: Vec<_>| v.iter().join("").parse().unwrap();
let part2 = range(win_bounds(concat(times), concat(records)));
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}

142
src/bin/day07.rs Normal file
View File

@@ -0,0 +1,142 @@
use std::env;
use std::fs;
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
enum Kind {
HC,
OP,
TP,
THREE,
FH,
FOUR,
FIVE,
}
impl Kind {
fn from_sorted_frequencies(slice: &[u8]) -> Kind {
match slice {
&[5] => Kind::FIVE,
&[4, _] => Kind::FOUR,
&[3, _] => Kind::FH,
&[3, _, _] => Kind::THREE,
&[2, _, _] => Kind::TP,
&[2, _, _, _] => Kind::OP,
&[1, _, _, _, _] => Kind::HC,
_ => unreachable!(),
}
}
}
fn find_frequencies(hand: [u8; 5]) -> (u8, [u8; 5], [u8; 5]) {
let mut distinct = 0u8;
let mut cards = [0u8; 5];
let mut counts = [0u8; 5];
for c in hand {
let mut was_seen: bool = false;
for i in 0..5 {
if c == cards[i] {
counts[i] += 1;
was_seen = true;
break;
}
}
if !was_seen {
cards[distinct as usize] = c;
counts[distinct as usize] = 1;
distinct += 1;
}
}
(distinct, cards, counts)
}
fn kind(hand: [u8; 5]) -> Kind {
let (distinct, _, mut counts) = find_frequencies(hand);
counts.sort();
counts.reverse();
Kind::from_sorted_frequencies(&counts[..distinct as usize])
}
fn joker_kind(hand: [u8; 5], joker: u8) -> Kind {
let (mut distinct, cards, mut counts) = find_frequencies(hand);
let mut joker_count = 0;
for c in 0..distinct as usize {
if cards[c] == joker {
joker_count += counts[c];
distinct -= 1;
counts[c] = 0;
break;
}
}
distinct = distinct.max(1);
counts.sort();
counts.reverse();
counts[0] += joker_count;
Kind::from_sorted_frequencies(&counts[..distinct as usize])
}
fn solve(encoding: impl Fn(u8) -> u8, kind_eval: impl Fn([u8; 5]) -> Kind, input: &str) -> u64 {
let mut hand_bid_pairs: Vec<_> = input
.lines()
.map(|line| {
let mut hand: [u8; 5] = (&line.as_bytes()[..5]).try_into().unwrap();
for b in hand.iter_mut() {
*b = encoding(*b);
}
let kind = kind_eval(hand);
let bid = line
.bytes()
.skip(6)
.map(|c| (c - b'0') as u64)
.reduce(|a, b| a * 10 + b)
.unwrap();
(kind, hand, bid)
})
.collect();
hand_bid_pairs.sort();
hand_bid_pairs
.into_iter()
.zip(1..)
.map(|((_, _, bid), i)| bid * i)
.sum()
}
fn main() {
let filename = env::args().nth(1).unwrap();
let input = fs::read_to_string(filename).unwrap();
let encode_p1 = |byte| match byte {
b'T' => b'V',
b'J' => b'W',
b'Q' => b'X',
b'K' => b'Y',
b'A' => b'Z',
_ => byte,
};
let part1 = solve(encode_p1, kind, &input);
let encode_p2 = |byte| match byte {
b'T' => b'V',
b'J' => b'1',
b'Q' => b'X',
b'K' => b'Y',
b'A' => b'Z',
_ => byte,
};
let kind_eval = |hand| joker_kind(hand, encode_p2(b'J'));
let part2 = solve(encode_p2, kind_eval, &input);
println!("1) {}", part1);
println!("2) {}", part2);
}

49
src/bin/day08.rs Normal file
View File

@@ -0,0 +1,49 @@
use gcd;
use hashbrown::{HashMap, HashSet};
use anyhow::{Context, Result};
fn lcm(a: u64, b: u64) -> u64 {
(a * b) / gcd::binary_u64(a, b)
}
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day08 <path to puzle input>")?;
let input = std::fs::read_to_string(filename)?;
let mut lines = input.lines().map(|line| line.as_bytes());
let instructions = lines.next().unwrap();
let connections: Vec<_> = lines.skip(1).collect();
let sources = connections.iter().map(|c| &c[..3]);
let name_ids: HashMap<_, _> = sources.clone().zip(0..).collect();
let id = |name: &[u8]| name_ids[name];
let mut neighbours = vec![(0u32, 0u32); connections.len()];
for (from_id, conn) in connections.iter().enumerate() {
neighbours[from_id] = (id(&conn[7..10]), id(&conn[12..15]));
}
let cycle_len = |start: &[u8], ends: &HashSet<_>| -> u64 {
let mut step_count = 0;
let mut pos = id(start);
let mut plan = instructions.iter().cycle();
while !ends.contains(&pos) {
let (to_l, to_r) = neighbours[pos as usize];
pos = if plan.next().unwrap() == &b'L' { to_l } else { to_r };
step_count += 1;
}
step_count
};
let ends = sources.clone().filter(|s| s[2] == b'Z').map(id).collect();
let part1 = cycle_len("AAA".as_bytes(), &ends);
let starts = sources.filter(|s| s[2] == b'A');
let part2 = starts.map(|pos| cycle_len(pos, &ends)).reduce(lcm).unwrap();
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}

48
src/bin/day09.rs Normal file
View File

@@ -0,0 +1,48 @@
use anyhow::{Context, Result};
fn extrapolate(sequence: &mut Vec<i64>) -> Option<(i64, i64)> {
let diff = |seq: &mut Vec<i64>| {
(0..seq.len() - 1).for_each(|i| seq[i] = seq[i + 1] - seq[i]);
seq.pop();
};
let mut fwd = 0;
let mut bwd = 0;
let mut f = 1;
while sequence.len() > 1 {
fwd += sequence[sequence.len() - 1];
bwd += f * sequence[0];
diff(sequence);
f *= -1;
if sequence.iter().all(|&num| num == 0) {
return Some((bwd, fwd));
}
}
None
}
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day09 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let mut part1 = 0;
let mut part2 = 0;
for line in input.lines() {
let parse_i64 = |str: &str| str.parse::<i64>().unwrap();
let mut initial: Vec<i64> = line.split_ascii_whitespace().map(parse_i64).collect();
let (p2, p1) = extrapolate(&mut initial).context("malformed input")?;
part1 += p1;
part2 += p2;
}
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}

184
src/bin/day10.rs Normal file
View File

@@ -0,0 +1,184 @@
use std::env;
use std::fs;
use anyhow::{anyhow, Context, Result};
#[derive(PartialEq, Eq, Copy, Clone, PartialOrd, Ord, Debug)]
struct Point {
x: usize,
y: usize,
}
impl<T> From<(T, T)> for Point
where
T: Into<usize>,
{
fn from(p: (T, T)) -> Self {
return Point {
x: p.0.into(),
y: p.1.into(),
};
}
}
impl Point {
#[rustfmt::skip]
fn walk(&self, d: Dir) -> Self {
match d {
Dir::U => Self { x: self.x - 1, y: self.y },
Dir::D => Self { x: self.x + 1, y: self.y },
Dir::L => Self { x: self.x, y: self.y - 1 },
Dir::R => Self { x: self.x, y: self.y + 1 },
}
}
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
enum Dir {
U,
D,
L,
R,
}
impl Dir {
fn invert(&self) -> Dir {
match self {
Dir::U => Dir::D,
Dir::D => Dir::U,
Dir::L => Dir::R,
Dir::R => Dir::L,
}
}
}
struct Pipe {
end1: Dir,
end2: Dir,
}
impl TryFrom<u8> for Pipe {
type Error = anyhow::Error;
#[rustfmt::skip]
fn try_from(byte: u8) -> Result<Self, Self::Error> {
match byte {
b'|' => Ok(Pipe { end1: Dir::D, end2: Dir::U }),
b'-' => Ok(Pipe { end1: Dir::L, end2: Dir::R }),
b'F' => Ok(Pipe { end1: Dir::D, end2: Dir::R }),
b'7' => Ok(Pipe { end1: Dir::D, end2: Dir::L }),
b'J' => Ok(Pipe { end1: Dir::U, end2: Dir::L }),
b'L' => Ok(Pipe { end1: Dir::U, end2: Dir::R }),
_ => Err(anyhow!("{} ({}) is not a pipe,", byte as char, byte)),
}
}
}
impl Pipe {
fn other_end(&self, from: &Dir) -> Option<Dir> {
if *from == self.end1 {
Some(self.end2)
} else if *from == self.end2 {
Some(self.end1)
} else {
None
}
}
}
struct Grid<T> {
grid: Vec<T>,
width: usize,
}
impl<T> std::ops::Index<Point> for Grid<T> {
type Output = T;
fn index(&self, p: Point) -> &T {
&self.grid[p.x * self.width + p.y]
}
}
impl<T> std::ops::IndexMut<Point> for Grid<T> {
fn index_mut(&mut self, p: Point) -> &mut T {
&mut self.grid[p.x * self.width + p.y]
}
}
fn main() -> Result<()> {
let filename = env::args()
.nth(1)
.context("./day10 <path to puzzle input>")?;
let input = fs::read_to_string(filename)?;
let width = input.lines().next().context("bad input")?.trim().len();
let grid: Vec<_> = input.lines().flat_map(|line| line.trim().bytes()).collect();
let height = grid.len() / width;
let grid = Grid { grid, width };
let mut all_points = (0..height).flat_map(|x| (0..width).map(move |y| (x, y).into()));
let start: Point = all_points
.find(|point: &Point| grid[point.clone()] == b'S')
.context("start not found")?;
let mut start_directions = Vec::new();
for dir in [Dir::U, Dir::D, Dir::L, Dir::R] {
let neighbour = start.walk(dir);
let byte = grid[neighbour];
if byte != b'.' {
let pipe = Pipe::try_from(byte)?;
if pipe.end1 == dir.invert() || pipe.end2 == dir.invert() {
start_directions.push(dir);
}
}
}
assert_eq!(start_directions.len(), 2);
let mut on_loop = Grid {
grid: vec![false; height * width],
width,
};
let end = start.walk(start_directions[0]);
on_loop[end] = true;
on_loop[start] = true;
let mut loop_length = 2;
let mut entry: Dir = start_directions[1].invert();
let mut position: Point = start.walk(start_directions[1]);
while position != end {
loop_length += 1;
on_loop[position] = true;
let pipe = Pipe::try_from(grid[position])?;
let exit = pipe.other_end(&entry).context("broken pipe")?;
position = position.walk(exit);
entry = exit.invert();
}
let is_vertical = |b| b == b'|' || b == b'L' || b == b'J';
let mut enclosed_count = 0;
for x in 0..height {
let mut parity: bool = false;
for y in 0..width {
let byte = grid.grid[x * width + y];
if !on_loop.grid[x * width + y] {
enclosed_count += parity as usize;
} else {
if is_vertical(byte) || byte == b'S' {
parity = !parity;
}
}
}
}
println!("1) {}", loop_length / 2);
println!("2) {}", enclosed_count);
Ok(())
}

43
src/bin/day11.rs Normal file
View File

@@ -0,0 +1,43 @@
use anyhow::{Context, Result};
use itertools::Itertools;
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day11 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let lines: Vec<_> = input.lines().map(|line| line.as_bytes()).collect();
let height = lines.len();
let width = lines[0].len();
let galaxies: Vec<_> = (0..height)
.cartesian_product(0..width)
.filter(|&(x, y)| lines[x][y] == b'#')
.collect();
let mut is_empty_row = vec![true; height];
let mut is_empty_col = vec![true; width];
galaxies.iter().for_each(|&(x, y)| {
is_empty_row[x] = false;
is_empty_col[y] = false;
});
let empty_rows_count = |a: usize, b| (a.min(b)..a.max(b)).filter(|&x| is_empty_row[x]).count();
let empty_cols_count = |a: usize, b| (a.min(b)..a.max(b)).filter(|&y| is_empty_col[y]).count();
let distance = |(&(x1, y1), &(x2, y2)): (&(usize, usize), &(usize, usize)), scale: usize| {
let emptiness = empty_rows_count(x1, x2) + empty_cols_count(y1, y2);
x1.abs_diff(x2) + y1.abs_diff(y2) + (scale - 1) * emptiness
};
let galaxy_pairs = galaxies.iter().tuple_combinations();
let part1: usize = galaxy_pairs.clone().map(|p| distance(p, 2)).sum();
let part2: usize = galaxy_pairs.clone().map(|p| distance(p, 1000000)).sum();
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}

99
src/bin/day12.rs Normal file
View File

@@ -0,0 +1,99 @@
use hashbrown::HashMap;
use ndarray::Array3;
use anyhow::{Context, Result};
fn solve(str: &[u8], groups: &[usize]) -> usize {
if groups.iter().sum::<usize>() + groups.len() > str.len() + 1 {
return 0;
}
let groups_max = groups.iter().max().unwrap();
let mut dp = Array3::<usize>::zeros((str.len() + 1, groups.len() + 1, *groups_max + 1));
dp[[0, 0, 0]] = 1;
for s in 0..str.len() {
if str[s] == b'.' || str[s] == b'?' {
for g in 0..groups.len() {
dp[[s + 1, g, 0]] += dp[[s, g, 0]];
dp[[s + 1, g + 1, 0]] += dp[[s, g, groups[g]]];
}
dp[[s + 1, groups.len(), 0]] += dp[[s, groups.len(), 0]];
}
if str[s] == b'#' || str[s] == b'?' {
for g in 0..groups.len() {
for p in 0..groups[g] {
dp[[s + 1, g, p + 1]] += dp[[s, g, p]];
}
}
}
}
dp[[str.len(), groups.len(), 0]] + dp[[str.len(), groups.len() - 1, groups[groups.len() - 1]]]
}
type Cache<'a> = HashMap<(&'a [u8], &'a [usize], usize), usize>;
fn solve_rec<'a>(str: &'a [u8], groups: &'a [usize], pos: usize, cache: &mut Cache<'a>) -> usize {
let mut ans = 0;
if let Some(result) = cache.get(&(str, groups, pos)) {
return *result;
}
if str.len() == 0 {
return (groups.len() == 0 || (groups.len() == 1 && pos == groups[0])) as usize;
}
if str[0] == b'.' || str[0] == b'?' {
if groups.len() == 0 || pos == 0 {
ans += solve_rec(&str[1..], groups, pos, cache);
} else if pos == groups[0] {
ans += solve_rec(&str[1..], &groups[1..], 0, cache);
}
}
if str[0] == b'#' || str[0] == b'?' {
if groups.len() > 0 && pos < groups[0] {
ans += solve_rec(&str[1..], groups, pos + 1, cache);
}
}
cache.insert((str, groups, pos), ans);
return ans;
}
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day12 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let mut part1 = 0;
let mut part2 = 0;
for line in input.lines() {
let (record, groups) = line.split_once(' ').context("malformed input")?;
let groups: Vec<usize> = groups.split(',').map(|v| v.parse().unwrap()).collect();
part1 += solve_rec(record.as_bytes(), &groups, 0, &mut HashMap::new());
let mut record5 = record.to_string();
let mut groups5 = groups.clone();
record5.reserve_exact(4 * (record.len() + 1));
groups5.reserve_exact(4 * groups.len());
for _ in 0..4 {
record5.push('?');
record5.push_str(record);
groups5.extend(&groups);
}
part2 += solve(record5.as_bytes(), &groups5);
}
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}

47
src/bin/day13.rs Normal file
View File

@@ -0,0 +1,47 @@
use anyhow::{Context, Result};
fn grid_count(x_max: usize, y_max: usize, predicate: impl Fn(usize, usize) -> bool) -> usize {
let mut count = 0;
for x in 0..x_max {
for y in 0..y_max {
count += predicate(x, y) as usize;
}
}
count
}
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day13 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let mut part1 = 0;
let mut part2 = 0;
for section in input.split("\n\n") {
let height = section.lines().count();
let grid: Vec<_> = section.lines().flat_map(|line| line.bytes()).collect();
let width = grid.len() / height;
let is_smudge_h = |i, x, y| grid[(i + x) * width + y] != grid[(i - x - 1) * width + y];
let is_smudge_v = |i, x, y| grid[x * width + i + y] != grid[x * width + (i - y - 1)];
for i in 1..height {
let smudge_count = grid_count(i.min(height - i), width, |x, y| is_smudge_h(i, x, y));
part1 += 100 * i * usize::from(smudge_count == 0);
part2 += 100 * i * usize::from(smudge_count == 1);
}
for i in 1..width {
let smudge_count = grid_count(height, i.min(width - i), |x, y| is_smudge_v(i, x, y));
part1 += i * usize::from(smudge_count == 0);
part2 += i * usize::from(smudge_count == 1);
}
}
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}

104
src/bin/day14.rs Normal file
View File

@@ -0,0 +1,104 @@
use hashbrown::HashMap;
use itertools::iproduct;
fn tilt_hori<I>(mut grid: Vec<u8>, h: usize, w: usize, range: I, y_delta: i64) -> Vec<u8>
where
I: Iterator<Item = (usize, usize)>,
{
let d = if y_delta == 1 { 0 } else { w - 1 };
let mut next_free = vec![d; h];
for (x, y) in range {
if grid[x * w + y] == b'#' {
next_free[x] = (y as i64 + y_delta) as usize;
}
if grid[x * w + y] == b'O' {
grid[x * w + y] = b'.';
grid[x * w + next_free[x]] = b'O';
next_free[x] = (next_free[x] as i64 + y_delta) as usize;
}
}
grid
}
fn tilt_vert<I>(mut grid: Vec<u8>, h: usize, w: usize, range: I, x_delta: i64) -> Vec<u8>
where
I: Iterator<Item = (usize, usize)>,
{
let d = if x_delta == 1 { 0 } else { h - 1 };
let mut next_free = vec![d; w];
for (x, y) in range {
if grid[x * w + y] == b'#' {
next_free[y] = (x as i64 + x_delta) as usize;
}
if grid[x * w + y] == b'O' {
grid[x * w + y] = b'.';
grid[next_free[y] * w + y] = b'O';
next_free[y] = (next_free[y] as i64 + x_delta) as usize;
}
}
grid
}
fn spin(mut grid: Vec<u8>, h: usize, w: usize) -> Vec<u8> {
grid = tilt_vert(grid, h, w, iproduct!(0..h, 0..w), 1); // north
grid = tilt_hori(grid, h, w, iproduct!(0..h, 0..w), 1); // west
grid = tilt_vert(grid, h, w, iproduct!((0..h).rev(), (0..w).rev()), -1); // south
grid = tilt_hori(grid, h, w, iproduct!((0..h).rev(), (0..w).rev()), -1); // east
return grid;
}
fn spin_n(mut grid: Vec<u8>, h: usize, w: usize, spin_count: usize) -> Vec<u8> {
let mut past: HashMap<(usize, usize), usize> = HashMap::new();
for spins in 0..spin_count {
let mut load = (0, 0);
for (x, y) in iproduct!(0..h, 0..w) {
if grid[x * w + y] == b'O' {
load.0 += h - x;
load.1 += w - y;
}
}
if let Some(first_seen) = past.get(&load) {
let offset = (spin_count - first_seen) % (spins - first_seen);
for _ in 0..offset {
grid = spin(grid, h, w);
}
return grid;
}
past.insert(load, spins);
grid = spin(grid, h, w);
}
grid
}
fn main() {
let filename = std::env::args().nth(1).unwrap();
let input = std::fs::read_to_string(filename).unwrap();
let w = input.lines().next().unwrap().len();
let grid: Vec<_> = input.lines().flat_map(|line| line.bytes()).collect();
let h = grid.len() / w;
let tilt_north = |grid: Vec<u8>| tilt_vert(grid, h, w, iproduct!(0..h, 0..w), 1);
let boulder_load = |(x, y), grid: &Vec<u8>| {
if grid[x * w + y] == b'O' {
h - x
} else {
0
}
};
let north_load = |grid| -> usize { iproduct!(0..h, 0..w).map(|p| boulder_load(p, grid)).sum() };
let grid1 = tilt_north(grid.clone());
let part1 = north_load(&grid1);
println!("{}", part1);
let grid2 = spin_n(grid, h, w, 1_000_000_000);
let part2 = north_load(&grid2);
println!("{}", part2);
}

46
src/bin/day15.rs Normal file
View File

@@ -0,0 +1,46 @@
use std::collections::HashMap;
use anyhow::{Context, Result};
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day15 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let sequence: Vec<_> = input.trim().split(',').map(|s| s.as_bytes()).collect();
let update = |state, c| (state + c as usize) * 17 % 256;
let hash = |w: &[u8]| w.iter().copied().fold(0, update);
let part1: usize = sequence.iter().map(|w| hash(w)).sum();
let mut buckets = vec![HashMap::new(); 256];
for (counter, step) in sequence.into_iter().enumerate() {
if step[step.len() - 1] == b'-' {
let key = &step[..step.len() - 1];
buckets[hash(key)].remove(key);
} else {
let (key, focal) = step.split_at(step.len() - 2);
let focal = focal[1] - b'0';
buckets[hash(key)]
.entry(key)
.and_modify(|(_, old_focal)| *old_focal = focal)
.or_insert((counter, focal));
}
}
let mut part2 = 0;
for (bucket, b_id) in buckets.into_iter().zip(1..) {
let mut v: Vec<_> = bucket.into_values().collect();
v.sort();
for ((_, focal), position) in v.into_iter().zip(1..) {
part2 += b_id * position * (focal as usize);
}
}
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}

78
src/bin/day17.rs Normal file
View File

@@ -0,0 +1,78 @@
use anyhow::{Context, Result};
fn min_heat_loss(grid: &Vec<u8>, h: i32, w: i32, min_steps: i32, max_steps: i32) -> Option<usize> {
let is_valid = |x, y| 0 <= x && x < h && 0 <= y && y < w;
let read = |x, y| (grid[(x * w + y) as usize] - b'0') as usize;
let bucket_count = ((max_steps * 9) as usize).next_power_of_two();
let mut queue = vec![Vec::new(); bucket_count];
queue[0].push(((0, 0), (0, 1)));
queue[0].push(((0, 0), (1, 0)));
let mut dist = vec![[usize::MAX, usize::MAX]; grid.len()];
let mut heat_loss = 0;
let mut queue_size = 2;
while queue_size > 0 {
while let Some(((x, y), (dx, dy))) = queue[heat_loss % bucket_count].pop() {
queue_size -= 1;
if x == h - 1 && y == w - 1 {
return Some(heat_loss);
}
if heat_loss > dist[(x * w + y) as usize][(dx == 0) as usize] {
continue;
}
for (dx, dy) in [(dy, -dx), (-dy, dx)] {
let (mut nx, mut ny) = (x, y);
let mut path_loss = 0;
for i in 1..=max_steps {
nx += dx;
ny += dy;
if !is_valid(nx, ny) {
break;
}
path_loss += read(nx, ny);
if i >= min_steps {
let is_horizontal = dx == 0;
let dist_pos = nx * w + ny;
let old_dist = dist[dist_pos as usize][is_horizontal as usize];
let new_dist = heat_loss + path_loss;
if new_dist < old_dist {
dist[dist_pos as usize][is_horizontal as usize] = new_dist;
queue[new_dist % bucket_count].push(((nx, ny), (dx, dy)));
queue_size += 1;
}
}
}
}
}
heat_loss += 1;
}
None
}
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day17 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let width = input.lines().next().unwrap().len();
let grid: Vec<_> = input.lines().flat_map(|l| l.bytes()).collect();
let height = grid.len() / width;
let part1 = min_heat_loss(&grid, height as i32, width as i32, 1, 3);
let part2 = min_heat_loss(&grid, height as i32, width as i32, 4, 10);
println!("1) {:?}", part1);
println!("2) {:?}", part2);
Ok(())
}

56
src/bin/day18.rs Normal file
View File

@@ -0,0 +1,56 @@
use std::str;
use anyhow::{Context, Result};
fn area(moves: impl Iterator<Item = ((i64, i64), i64)>) -> i64 {
let (mut x, mut y) = (0i64, 0i64);
let mut area = 0;
for ((dx, dy), dist) in moves {
let (nx, ny) = (dist * dx + x, dist * dy + y);
area += x * ny - y * nx;
area -= i64::abs(nx - x) + i64::abs(ny - y);
(x, y) = (nx, ny);
}
i64::abs(area / 2) + 1
}
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day18 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let lines = input.lines().map(|line| line.as_bytes());
let dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)];
let to_dir = |c| match c {
b'R' => dirs[0],
b'L' => dirs[2],
b'U' => dirs[3],
b'D' => dirs[1],
_ => unreachable!(),
};
let read_p1 = |line: &[u8]| {
let dir = line[0];
let dist = &line[2..line.len() - 10];
let dist = str::from_utf8(dist).unwrap().parse::<i64>().unwrap();
(to_dir(dir), dist)
};
let read_p2 = |line: &[u8]| {
let dir = line[line.len() - 2] - b'0';
let dist = &line[line.len() - 7..line.len() - 2];
let dist = str::from_utf8(dist).unwrap();
let dist = i64::from_str_radix(dist, 16).unwrap();
(dirs[dir as usize], dist)
};
let part1 = area(lines.clone().map(read_p1));
let part2 = area(lines.clone().map(read_p2));
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}

160
src/bin/day19.rs Normal file
View File

@@ -0,0 +1,160 @@
use std::cmp::Ordering;
use std::collections::HashMap;
use itertools::Itertools;
use anyhow::{Context, Result};
const MALFORMED: &str = "malformed input";
struct Rule {
property: u8,
comp: Ordering,
value: u32,
}
struct Workflow<'a> {
rules: Vec<(Rule, &'a str)>,
default: &'a str,
}
impl<'a> TryFrom<&'a str> for Workflow<'a> {
type Error = anyhow::Error;
fn try_from(str: &'a str) -> Result<Self> {
match str.rsplit_once(',') {
Some((rules_str, default)) => {
let mut rules = Vec::new();
for rule in rules_str.split(',') {
let (condition, dest) = rule.split_once(':').context("MALFORMED")?;
let property = index(condition.as_bytes()[0]);
let comp = condition.as_bytes()[1].cmp(&b'=');
let value = (&condition[2..]).parse()?;
rules.push((
Rule {
property,
comp,
value,
},
dest,
));
}
Ok(Workflow { rules, default })
}
None => Ok(Workflow {
rules: Vec::new(),
default: str,
}),
}
}
}
impl<'a> Workflow<'a> {
fn process(&self, state: &[u32; 4]) -> &'a str {
for (rule, dst) in self.rules.iter() {
if state[rule.property as usize].cmp(&rule.value) == rule.comp {
return dst;
}
}
return self.default;
}
}
fn index(b: u8) -> u8 {
match b {
b'x' => 0,
b'm' => 1,
b'a' => 2,
b's' => 3,
_ => unreachable!(),
}
}
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day19 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let (workflows_str, parts_str) = input.split("\n\n").next_tuple().context(MALFORMED)?;
let mut workflows = HashMap::new();
for workflow in workflows_str.lines() {
let (name, body) = workflow.split_once('{').context(MALFORMED)?;
workflows.insert(name, Workflow::try_from(&body[..body.len() - 1])?);
}
let mut parts = Vec::new();
for part_str in parts_str.lines() {
let mut part = [0, 0, 0, 0];
for (idx, property) in part_str[1..part.len() - 1].split(',').enumerate() {
part[idx] = property[2..].parse()?;
}
parts.push(part);
}
let mut part1: u64 = 0;
for part in parts.iter() {
let mut current = "in";
while (current != "A") & (current != "R") {
let workflow = workflows.get(&current).context(MALFORMED)?;
current = workflow.process(part);
}
if current == "A" {
part1 += part.iter().map(|&v| v as u64).sum::<u64>();
}
}
let split_bound = |rule: &Rule, bound: (u32, u32)| match rule.comp {
Ordering::Less => (
(bound.0, bound.1.min(rule.value)),
(bound.0.max(rule.value), bound.1),
),
Ordering::Greater => (
(bound.0.max(rule.value + 1), bound.1),
(bound.0, bound.1.min(rule.value + 1)),
),
_ => unreachable!(),
};
let mut part2 = 0;
let mut stack = Vec::new();
stack.push(("in", [(1, 4001), (1, 4001), (1, 4001), (1, 4001)]));
'outer: while let Some((current, mut bounds)) = stack.pop() {
if current == "A" {
part2 += bounds.iter().map(|(a, b)| (b - a) as u64).product::<u64>();
continue;
}
if current == "R" {
continue;
}
let workflow = workflows.get(&current).context(MALFORMED)?;
for (rule, dest) in workflow.rules.iter() {
let bound = bounds[rule.property as usize];
let (send, carry) = split_bound(rule, bound);
if send.0 < send.1 {
let mut new_bounds = bounds;
new_bounds[rule.property as usize] = send;
stack.push((dest, new_bounds));
}
if carry.0 < carry.1 {
bounds[rule.property as usize] = carry;
} else {
continue 'outer;
}
}
stack.push((workflow.default, bounds));
}
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}

175
src/bin/day20.rs Normal file
View File

@@ -0,0 +1,175 @@
use std::collections::VecDeque;
use anyhow::{Context, Result};
use hashbrown::{HashMap, HashSet};
#[derive(Clone)]
enum Gate<'a> {
FlipFlop {
high: bool,
},
Conjugation {
memory: HashSet<&'a str>,
in_count: usize,
},
Negate,
Broadcast,
}
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day20 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let mut gates: HashMap<&str, Gate> = HashMap::new();
let mut outs = HashMap::new();
let mut ins = HashMap::new();
for line in input.lines() {
let (source, dsts) = line.split_once(" -> ").context("malformed input.")?;
let dsts: Vec<_> = dsts.split(", ").collect();
let name = source.trim_start_matches(['&', '%']);
for dst in dsts.iter().copied() {
ins.entry(dst).or_insert(Vec::new()).push(name);
}
outs.insert(name, dsts);
let gate = match source.chars().next().unwrap() {
'%' => Gate::FlipFlop { high: false },
'&' => Gate::Conjugation {
memory: HashSet::new(),
in_count: 0,
},
'b' => Gate::Broadcast,
_ => unreachable!(),
};
gates.insert(name, gate);
}
for (name, gate) in gates.iter_mut() {
if let Gate::Conjugation {
memory: _,
in_count,
} = gate
{
*in_count = ins[name].len();
if *in_count == 1 {
*gate = Gate::Negate;
}
}
}
let mut counts = [0, 0];
let mut queue = VecDeque::new();
for _ in 0..1000 {
queue.push_back(("broadcaster", "button", false));
while let Some((dest, source, is_high)) = queue.pop_back() {
counts[is_high as usize] += 1;
let outs = match outs.get(dest) {
Some(v) => v,
None => {
continue;
}
};
if let Some(gate) = gates.get_mut(dest) {
match gate {
Gate::Broadcast => {
for out in outs {
queue.push_back((out, dest, false));
}
}
Gate::Negate => {
for out in outs {
queue.push_back((out, dest, !is_high));
}
}
Gate::FlipFlop { high } => {
if !is_high {
*high = !(*high);
for out in outs {
queue.push_back((out, dest, *high));
}
}
}
Gate::Conjugation { memory, in_count } => {
if is_high {
memory.insert(source);
} else {
memory.remove(source);
}
let signal = memory.len() < *in_count;
for out in outs {
queue.push_back((out, dest, signal));
}
}
}
}
}
}
let part1 = counts[0] * counts[1];
// Assumed structure:
// broadcast ----> COUNTER1 ----> NEGATE -----> CONJ ----> rx
// |----------> COUNTER2 ----> NEGATE --------|
// |----------> COUNTER3 ----> NEGATE --------|
// `----------> COUNTER4 ----> NEGATE --------´
//
// Where Counter:
// IN
// |
// v
// FF <-> Core --> OUT
// | ? ^
// V | |
// FF ?----´ |
// | |
// v |
// FF--------´
//
// Counters are assumed to trigger on prime values
//
let mut part2 = 1;
for mut root in outs["broadcaster"].iter().copied() {
let core = match &gates[&outs[root][0]] {
Gate::Conjugation {
memory: _,
in_count: _,
} => outs[root][0],
_ => outs[root][1],
};
let mut period = 0u64;
let mut exp = 0u64;
loop {
if outs[root].contains(&core) {
period += 1 << exp;
}
exp += 1;
root = match outs[root].iter().find(|&&v| v != core) {
Some(v) => v,
None => {
break;
}
};
}
part2 *= period;
}
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}

160
src/bin/day21.rs Normal file
View File

@@ -0,0 +1,160 @@
use anyhow::{Context, Result};
fn bfs(grid: &Vec<u8>, height: usize, width: usize, start: (i64, i64)) -> Vec<u64> {
let mut distances = vec![u64::MAX; grid.len()];
distances[start.0 as usize * width + start.1 as usize] = 0;
let height = height as i64;
let width = width as i64;
let mut todo = Vec::from([start]);
let mut next = Vec::new();
let mut distance = 0;
while !todo.is_empty() {
for (x, y) in todo.iter() {
for (dx, dy) in [(1, 0), (-1, 0), (0, 1), (0, -1)] {
let (nx, ny) = (x + dx, y + dy);
if 0 <= nx && nx < height && 0 <= ny && ny < width {
let index = (nx * width + ny) as usize;
if grid[index] != b'#' && distance + 1 < distances[index] {
distances[index] = distance + 1;
next.push((nx, ny));
}
}
}
}
todo.clear();
todo.append(&mut next);
distance += 1;
}
return distances;
}
fn repeat_grid(factor: usize, grid: &Vec<u8>, height: usize, width: usize) -> Vec<u8> {
let mut result = vec![0; height * width * factor * factor];
for xf in 0..factor {
for yf in 0..factor {
let base = xf * height * width * factor + yf * width;
for x in 0..height {
for y in 0..width {
result[base + x * width * factor + y] = grid[x * width + y];
}
}
}
}
return result;
}
fn main() -> Result<()> {
let filename = std::env::args()
.nth(1)
.context("./day21 <path to puzzle input>")?;
let input = std::fs::read_to_string(filename)?;
let width = input.lines().next().unwrap().len();
let grid: Vec<_> = input.lines().flat_map(|line| line.bytes()).collect();
let height = grid.len() / width;
let mut start1 = (0, 0);
for x in 0..height {
for y in 0..width {
if grid[x * width + y] == b'S' {
start1 = (x as i64, y as i64);
}
}
}
let distances = bfs(&grid, height, width, start1);
let steps_p1 = 64;
let mut part1 = 0;
for x in 0..height {
for y in 0..width {
let distance = distances[x * width + y];
if distance <= steps_p1 && distance % 2 == steps_p1 % 2 {
part1 += 1;
}
}
}
let center = (height as i64 / 2, width as i64 / 2);
let scale = 2usize;
let repeats = scale * 2 + 1;
let scale_center = (
center.0 + (scale * height) as i64,
center.1 + (scale * width) as i64,
);
let scale_grid = repeat_grid(repeats, &grid, height, width);
let distances = bfs(&scale_grid, height * repeats, width * repeats, scale_center);
let mut counts = vec![vec![0; repeats]; repeats];
let limit = (scale * height + height / 2) as u64;
for qx in 0..repeats {
for qy in 0..repeats {
let base = qx * height * width * repeats + qy * width;
for x in 0..height {
for y in 0..width {
let distance = distances[base + x * width * repeats + y];
if distance <= limit as u64 && limit as u64 % 2 == distance % 2 {
counts[qx][qy] += 1;
}
}
}
}
}
let part_2_steps = 26501365;
let part_2_scale: u64 = ((part_2_steps - height / 2) / height) as u64;
let mut part2 = 0;
for x in 0..repeats {
for y in 0..repeats {
print!("{: >6} ", counts[x][y]);
}
println!();
}
// ..*#*..
// .*'o'*.
// *'o+o'*
// #o+o+o#
// *'o+o'*
// .*'o'*.
// ..*#*..
// o Even centers
let inner_even_count = ((part_2_scale - 1) | 0x1) * ((part_2_scale - 1) | 0x1);
part2 += inner_even_count * counts[scale][scale];
let inner_odd_count = (part_2_scale & (!0x1)) * (part_2_scale & (!0x1));
// + Odd centers
part2 += inner_odd_count * counts[scale][scale + 1];
// # Pointy ends
part2 += counts[0][scale];
part2 += counts[repeats - 1][scale];
part2 += counts[scale][0];
part2 += counts[scale][repeats - 1];
// * Outer edges
part2 += part_2_scale * counts[0][scale - 1];
part2 += part_2_scale * counts[0][scale + 1];
part2 += part_2_scale * counts[repeats - 1][scale - 1];
part2 += part_2_scale * counts[repeats - 1][scale + 1];
// ' Inner edges
part2 += (part_2_scale - 1) * counts[1][scale - 1];
part2 += (part_2_scale - 1) * counts[1][scale + 1];
part2 += (part_2_scale - 1) * counts[repeats - 2][scale - 1];
part2 += (part_2_scale - 1) * counts[repeats - 2][scale + 1];
println!("1) {}", part1);
println!("2) {}", part2);
Ok(())
}