Up to Day 21 (excluding day 16)
This commit is contained in:
@@ -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
39
src/bin/day06.rs
Normal 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
142
src/bin/day07.rs
Normal 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
49
src/bin/day08.rs
Normal 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
48
src/bin/day09.rs
Normal 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
184
src/bin/day10.rs
Normal 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
43
src/bin/day11.rs
Normal 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
99
src/bin/day12.rs
Normal 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
47
src/bin/day13.rs
Normal 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
104
src/bin/day14.rs
Normal 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
46
src/bin/day15.rs
Normal 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
78
src/bin/day17.rs
Normal 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
56
src/bin/day18.rs
Normal 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
160
src/bin/day19.rs
Normal 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(¤t).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(¤t).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
175
src/bin/day20.rs
Normal 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
160
src/bin/day21.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user