From ca4fb1d3ad429048a12e75bced024a7d95c4039b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Sat, 26 Dec 2020 10:38:29 +0100 Subject: [PATCH] convert line endings to unix --- src/bin/day17.rs | 444 +++++++++++----------- src/bin/day18.rs | 338 ++++++++--------- src/bin/day19.rs | 406 ++++++++++----------- src/bin/day20.rs | 932 +++++++++++++++++++++++------------------------ src/bin/day21.rs | 114 +++--- src/bin/day22.rs | 196 +++++----- src/bin/day23.rs | 434 +++++++++++----------- src/bin/day24.rs | 280 +++++++------- src/bin/day25.rs | 88 ++--- 9 files changed, 1616 insertions(+), 1616 deletions(-) diff --git a/src/bin/day17.rs b/src/bin/day17.rs index 552cc47..63e209d 100644 --- a/src/bin/day17.rs +++ b/src/bin/day17.rs @@ -1,222 +1,222 @@ -use std::ops::Range; - -const INPUT: &str = include_str!("../../data/day17"); - -#[derive(Clone, Copy, Default)] -struct Dimension { - offset: u32, - size: u32, -} - -impl Dimension { - fn map(&self, pos: i32) -> Option { - let p = (self.offset as i32) + pos; - if p >= 0 && p < (self.size as i32) { - Some(p as usize) - } else { - None - } - } - - fn include(&self, pos: i32) -> Self { - let left = std::cmp::min(pos, -(self.offset as i32)); - let right = std::cmp::max(pos, (self.size - self.offset - 1) as i32); - Self { - offset: (-left) as u32, - size: (right - left + 1) as u32, - } - } - - fn extend(&self) -> Self { - Self { - offset: self.offset + 1, - size: self.size + 2, - } - } -} - -impl IntoIterator for Dimension { - type IntoIter = Range; - type Item = i32; - - fn into_iter(self) -> Self::IntoIter { - -(self.offset as i32)..((self.size - self.offset) as i32) - } -} - -#[derive(Clone)] -struct Field { - x_dim: Dimension, - y_dim: Dimension, - z_dim: Dimension, - w_dim: Dimension, - cells: Vec, -} - -impl Default for Field { - fn default() -> Self { - Self { - x_dim: Dimension { offset: 0, size: 1}, - y_dim: Dimension { offset: 0, size: 1}, - z_dim: Dimension { offset: 0, size: 1}, - w_dim: Dimension { offset: 0, size: 1}, - cells: vec![false], - } - } -} - -impl Field { - fn alloc(x_dim: Dimension, y_dim: Dimension, z_dim: Dimension, w_dim: Dimension) -> Self { - let mut cells = Vec::new(); - let size = (x_dim.size * y_dim.size * z_dim.size * w_dim.size) as usize; - cells.resize(size, false); - Self { x_dim, y_dim, z_dim, w_dim, cells } - } - - fn _addr(&self, x: i32, y: i32, z: i32, w: i32) -> Option { - let x = self.x_dim.map(x)?; - let y = self.y_dim.map(y)?; - let z = self.z_dim.map(z)?; - let w = self.w_dim.map(w)?; - let addr = ((w * (self.z_dim.size as usize) + z) * (self.y_dim.size as usize) + y) * (self.x_dim.size as usize) + x; - Some(addr) - } - - fn _get(&self, x: i32, y: i32, z: i32, w: i32) -> Option { - let addr = self._addr(x, y, z, w)?; - Some(self.cells[addr]) - } - - fn get(&self, x: i32, y: i32, z: i32, w: i32) -> bool { - self._get(x, y, z, w).unwrap_or(false) - } - - fn set(&mut self, x: i32, y: i32, z: i32, w: i32, value: bool) { - if let Some(addr) = self._addr(x, y, z, w) { - self.cells[addr] = value; - } else if value { - // don't resize to set "false" - let x_dim = self.x_dim.include(x); - let y_dim = self.y_dim.include(y); - let z_dim = self.z_dim.include(z); - let w_dim = self.w_dim.include(w); - let mut new_field = Self::alloc(x_dim, y_dim, z_dim, w_dim); - for w in self.w_dim { - for z in self.z_dim { - for y in self.y_dim { - for x in self.x_dim { - let addr = new_field._addr(x, y, z, w).unwrap(); - new_field.cells[addr] = self.get(x, y, z, w); - } - } - } - } - let addr = new_field._addr(x, y, z, w).unwrap(); - new_field.cells[addr] = value; - *self = new_field; - } - } - - fn parse(data: &str) -> Self { - let mut this = Self::default(); - for (y, line) in data.lines().enumerate() { - for (x, chr) in line.chars().enumerate() { - this.set(x as i32, y as i32, 0, 0, chr == '#'); - } - } - this - } - - fn count_active_neighbors(&self, x: i32, y: i32, z: i32, w: i32) -> u32 { - let mut count = 0; - for dw in -1..=1 { - for dz in -1..=1 { - for dy in -1..=1 { - for dx in -1..=1 { - if dw == 0 && dz == 0 && dy == 0 && dx == 0 { - continue; - } - if self.get(x + dx, y + dy, z + dz, w + dw) { - count += 1; - if count > 3 { return count; } // more than 3 is always bad - } - } - } - } - } - count - } - - fn cycle_3dim(&self) -> Self { - let mut result = self.clone(); - let w = 0; - for z in self.z_dim.extend() { - for y in self.y_dim.extend() { - for x in self.x_dim.extend() { - let count = self.count_active_neighbors(x, y, z, w); - let active = count == 3 || (count == 2 && self.get(x, y, z, w)); - result.set(x, y, z, w, active); - } - } - } - result - } - - fn cycle_4dim(&self) -> Self { - let mut result = self.clone(); - for w in self.w_dim.extend() { - for z in self.z_dim.extend() { - for y in self.y_dim.extend() { - for x in self.x_dim.extend() { - let count = self.count_active_neighbors(x, y, z, w); - let active = count == 3 || (count == 2 && self.get(x, y, z, w)); - result.set(x, y, z, w, active); - } - } - } - } - result - } -} - -impl std::fmt::Debug for Field { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use std::fmt::Write; - for z in self.z_dim { - for w in self.w_dim { - writeln!(f, "z = {}, w = {}", z, w)?; - for y in self.y_dim { - for x in self.x_dim { - if x == 0 && y == 0 { - f.write_char( - if self.get(x, y, z, w) { 'X' } else { '+' } - )?; - } else { - f.write_char( - if self.get(x, y, z, w) { '#' } else { '.' } - )?; - } - } - f.write_char('\n')?; - } - f.write_char('\n')?; - } - } - Ok(()) - } -} - -fn main() { - let input = Field::parse(INPUT); - let mut field = input.clone(); - for _ in 0..6 { - field = field.cycle_3dim(); - } - println!("Active after 6 3-dim rounds: {}", field.cells.iter().filter(|&&c| c).count()); - let mut field = input.clone(); - for _ in 0..6 { - field = field.cycle_4dim(); - } - println!("Active after 6 4-dim rounds: {}", field.cells.iter().filter(|&&c| c).count()); - println!("{:?}", input); -} +use std::ops::Range; + +const INPUT: &str = include_str!("../../data/day17"); + +#[derive(Clone, Copy, Default)] +struct Dimension { + offset: u32, + size: u32, +} + +impl Dimension { + fn map(&self, pos: i32) -> Option { + let p = (self.offset as i32) + pos; + if p >= 0 && p < (self.size as i32) { + Some(p as usize) + } else { + None + } + } + + fn include(&self, pos: i32) -> Self { + let left = std::cmp::min(pos, -(self.offset as i32)); + let right = std::cmp::max(pos, (self.size - self.offset - 1) as i32); + Self { + offset: (-left) as u32, + size: (right - left + 1) as u32, + } + } + + fn extend(&self) -> Self { + Self { + offset: self.offset + 1, + size: self.size + 2, + } + } +} + +impl IntoIterator for Dimension { + type IntoIter = Range; + type Item = i32; + + fn into_iter(self) -> Self::IntoIter { + -(self.offset as i32)..((self.size - self.offset) as i32) + } +} + +#[derive(Clone)] +struct Field { + x_dim: Dimension, + y_dim: Dimension, + z_dim: Dimension, + w_dim: Dimension, + cells: Vec, +} + +impl Default for Field { + fn default() -> Self { + Self { + x_dim: Dimension { offset: 0, size: 1}, + y_dim: Dimension { offset: 0, size: 1}, + z_dim: Dimension { offset: 0, size: 1}, + w_dim: Dimension { offset: 0, size: 1}, + cells: vec![false], + } + } +} + +impl Field { + fn alloc(x_dim: Dimension, y_dim: Dimension, z_dim: Dimension, w_dim: Dimension) -> Self { + let mut cells = Vec::new(); + let size = (x_dim.size * y_dim.size * z_dim.size * w_dim.size) as usize; + cells.resize(size, false); + Self { x_dim, y_dim, z_dim, w_dim, cells } + } + + fn _addr(&self, x: i32, y: i32, z: i32, w: i32) -> Option { + let x = self.x_dim.map(x)?; + let y = self.y_dim.map(y)?; + let z = self.z_dim.map(z)?; + let w = self.w_dim.map(w)?; + let addr = ((w * (self.z_dim.size as usize) + z) * (self.y_dim.size as usize) + y) * (self.x_dim.size as usize) + x; + Some(addr) + } + + fn _get(&self, x: i32, y: i32, z: i32, w: i32) -> Option { + let addr = self._addr(x, y, z, w)?; + Some(self.cells[addr]) + } + + fn get(&self, x: i32, y: i32, z: i32, w: i32) -> bool { + self._get(x, y, z, w).unwrap_or(false) + } + + fn set(&mut self, x: i32, y: i32, z: i32, w: i32, value: bool) { + if let Some(addr) = self._addr(x, y, z, w) { + self.cells[addr] = value; + } else if value { + // don't resize to set "false" + let x_dim = self.x_dim.include(x); + let y_dim = self.y_dim.include(y); + let z_dim = self.z_dim.include(z); + let w_dim = self.w_dim.include(w); + let mut new_field = Self::alloc(x_dim, y_dim, z_dim, w_dim); + for w in self.w_dim { + for z in self.z_dim { + for y in self.y_dim { + for x in self.x_dim { + let addr = new_field._addr(x, y, z, w).unwrap(); + new_field.cells[addr] = self.get(x, y, z, w); + } + } + } + } + let addr = new_field._addr(x, y, z, w).unwrap(); + new_field.cells[addr] = value; + *self = new_field; + } + } + + fn parse(data: &str) -> Self { + let mut this = Self::default(); + for (y, line) in data.lines().enumerate() { + for (x, chr) in line.chars().enumerate() { + this.set(x as i32, y as i32, 0, 0, chr == '#'); + } + } + this + } + + fn count_active_neighbors(&self, x: i32, y: i32, z: i32, w: i32) -> u32 { + let mut count = 0; + for dw in -1..=1 { + for dz in -1..=1 { + for dy in -1..=1 { + for dx in -1..=1 { + if dw == 0 && dz == 0 && dy == 0 && dx == 0 { + continue; + } + if self.get(x + dx, y + dy, z + dz, w + dw) { + count += 1; + if count > 3 { return count; } // more than 3 is always bad + } + } + } + } + } + count + } + + fn cycle_3dim(&self) -> Self { + let mut result = self.clone(); + let w = 0; + for z in self.z_dim.extend() { + for y in self.y_dim.extend() { + for x in self.x_dim.extend() { + let count = self.count_active_neighbors(x, y, z, w); + let active = count == 3 || (count == 2 && self.get(x, y, z, w)); + result.set(x, y, z, w, active); + } + } + } + result + } + + fn cycle_4dim(&self) -> Self { + let mut result = self.clone(); + for w in self.w_dim.extend() { + for z in self.z_dim.extend() { + for y in self.y_dim.extend() { + for x in self.x_dim.extend() { + let count = self.count_active_neighbors(x, y, z, w); + let active = count == 3 || (count == 2 && self.get(x, y, z, w)); + result.set(x, y, z, w, active); + } + } + } + } + result + } +} + +impl std::fmt::Debug for Field { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::fmt::Write; + for z in self.z_dim { + for w in self.w_dim { + writeln!(f, "z = {}, w = {}", z, w)?; + for y in self.y_dim { + for x in self.x_dim { + if x == 0 && y == 0 { + f.write_char( + if self.get(x, y, z, w) { 'X' } else { '+' } + )?; + } else { + f.write_char( + if self.get(x, y, z, w) { '#' } else { '.' } + )?; + } + } + f.write_char('\n')?; + } + f.write_char('\n')?; + } + } + Ok(()) + } +} + +fn main() { + let input = Field::parse(INPUT); + let mut field = input.clone(); + for _ in 0..6 { + field = field.cycle_3dim(); + } + println!("Active after 6 3-dim rounds: {}", field.cells.iter().filter(|&&c| c).count()); + let mut field = input.clone(); + for _ in 0..6 { + field = field.cycle_4dim(); + } + println!("Active after 6 4-dim rounds: {}", field.cells.iter().filter(|&&c| c).count()); + println!("{:?}", input); +} diff --git a/src/bin/day18.rs b/src/bin/day18.rs index a4ca796..07030a9 100644 --- a/src/bin/day18.rs +++ b/src/bin/day18.rs @@ -1,169 +1,169 @@ -const INPUT: &str = include_str!("../../data/day18"); - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum Op { - Sum, - Mul, -} - -impl Op { - fn apply(self, a: T, b: T) -> T - where - T: std::ops::Add + std::ops::Mul, - { - match self { - Self::Sum => a + b, - Self::Mul => a * b, - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum Elem { - ParenLeft, - Op(Op), - Item(T), -} - -impl Elem { - fn expect_item(self) -> T { - if let Elem::Item(item) = self { - item - } else { - panic!("Expected item"); - } - } - - fn expect_op(self) -> Op { - if let Elem::Op(op) = self { - op - } else { - panic!("Expected op"); - } - } - - fn expect_paren_left(self) { - if let Elem::ParenLeft = self { - () - } else { - panic!("Expected '('"); - } - } -} - -#[derive(Debug)] -struct Stack { - elems: Vec>, - same_precedence: bool, -} - -impl Stack -where - T: std::ops::Add + std::ops::Mul + std::fmt::Debug, -{ - fn new(same_precedence: bool) -> Self { - Self { - elems: Vec::new(), - same_precedence, - } - } - - fn push_item(&mut self, item: T) { - self.elems.push(Elem::Item(item)) - } - - fn push_op(&mut self, op: Op) { - self.elems.push(Elem::Op(op)) - } - - fn push_paren_left(&mut self) { - self.elems.push(Elem::ParenLeft) - } - - fn _peek_top(&self) -> &Elem { - &self.elems[self.elems.len() - 1] - } - - fn _peek_below_top(&self) -> &Elem { - &self.elems[self.elems.len() - 2] - } - - fn pop_item(&mut self) -> T { - self.elems.pop().expect("expect non empty stack").expect_item() - } - - fn pop_op(&mut self) -> Op { - self.elems.pop().expect("expect non empty stack").expect_op() - } - - fn pop_paren_left(&mut self) { - self.elems.pop().expect("expect non empty stack").expect_paren_left() - } - - fn _do_reduce_op(&self, finish: bool) -> bool { - match self._peek_below_top() { - Elem::Op(Op::Sum) => true, - Elem::Op(Op::Mul) => finish || self.same_precedence, - _ => false, - } - } - - fn is_empty(&self) -> bool { - self.elems.is_empty() - } - - fn eval_top(&mut self, finish: bool) { - while self.elems.len() > 2 && matches!(self._peek_top(), Elem::Item(_)) && self._do_reduce_op(finish) { - let value2 = self.pop_item(); - let op = self.pop_op(); - let value1 = self.pop_item(); - self.push_item(op.apply(value1, value2)); - } - } -} - -fn parse(mut line: &str, same_precedence: bool) -> T -where - T: std::ops::Add + std::ops::Mul + std::str::FromStr + std::fmt::Debug, - ::Err: std::fmt::Debug, -{ - let mut stack = Stack::new(same_precedence); - - loop { - line = line.trim(); - if line.is_empty() { - break; - } - if let Some(rem) = line.strip_prefix('+') { - stack.push_op(Op::Sum); - line = rem; - } else if let Some(rem) = line.strip_prefix('*') { - stack.push_op(Op::Mul); - line = rem; - } else if let Some(rem) = line.strip_prefix('(') { - stack.push_paren_left(); - line = rem; - } else if let Some(rem) = line.strip_prefix(')') { - stack.eval_top(true); - let item = stack.pop_item(); - stack.pop_paren_left(); - stack.push_item(item); - line = rem; - } else { - let num_end = line.find(|c: char| !c.is_ascii_digit()).unwrap_or(line.len()); - let num = line[..num_end].parse::().unwrap(); - stack.push_item(num); - line = &line[num_end..]; - } - stack.eval_top(false); - } - stack.eval_top(true); - let item = stack.pop_item(); - assert!(stack.is_empty()); - item -} - -fn main() { - println!("Sum of evaluated lines (simple math): {}", INPUT.lines().map(|l| parse::(l, true)).sum::()); - println!("Sum of evaluated lines (advanced math): {}", INPUT.lines().map(|l| parse::(l, false)).sum::()); -} +const INPUT: &str = include_str!("../../data/day18"); + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Op { + Sum, + Mul, +} + +impl Op { + fn apply(self, a: T, b: T) -> T + where + T: std::ops::Add + std::ops::Mul, + { + match self { + Self::Sum => a + b, + Self::Mul => a * b, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Elem { + ParenLeft, + Op(Op), + Item(T), +} + +impl Elem { + fn expect_item(self) -> T { + if let Elem::Item(item) = self { + item + } else { + panic!("Expected item"); + } + } + + fn expect_op(self) -> Op { + if let Elem::Op(op) = self { + op + } else { + panic!("Expected op"); + } + } + + fn expect_paren_left(self) { + if let Elem::ParenLeft = self { + () + } else { + panic!("Expected '('"); + } + } +} + +#[derive(Debug)] +struct Stack { + elems: Vec>, + same_precedence: bool, +} + +impl Stack +where + T: std::ops::Add + std::ops::Mul + std::fmt::Debug, +{ + fn new(same_precedence: bool) -> Self { + Self { + elems: Vec::new(), + same_precedence, + } + } + + fn push_item(&mut self, item: T) { + self.elems.push(Elem::Item(item)) + } + + fn push_op(&mut self, op: Op) { + self.elems.push(Elem::Op(op)) + } + + fn push_paren_left(&mut self) { + self.elems.push(Elem::ParenLeft) + } + + fn _peek_top(&self) -> &Elem { + &self.elems[self.elems.len() - 1] + } + + fn _peek_below_top(&self) -> &Elem { + &self.elems[self.elems.len() - 2] + } + + fn pop_item(&mut self) -> T { + self.elems.pop().expect("expect non empty stack").expect_item() + } + + fn pop_op(&mut self) -> Op { + self.elems.pop().expect("expect non empty stack").expect_op() + } + + fn pop_paren_left(&mut self) { + self.elems.pop().expect("expect non empty stack").expect_paren_left() + } + + fn _do_reduce_op(&self, finish: bool) -> bool { + match self._peek_below_top() { + Elem::Op(Op::Sum) => true, + Elem::Op(Op::Mul) => finish || self.same_precedence, + _ => false, + } + } + + fn is_empty(&self) -> bool { + self.elems.is_empty() + } + + fn eval_top(&mut self, finish: bool) { + while self.elems.len() > 2 && matches!(self._peek_top(), Elem::Item(_)) && self._do_reduce_op(finish) { + let value2 = self.pop_item(); + let op = self.pop_op(); + let value1 = self.pop_item(); + self.push_item(op.apply(value1, value2)); + } + } +} + +fn parse(mut line: &str, same_precedence: bool) -> T +where + T: std::ops::Add + std::ops::Mul + std::str::FromStr + std::fmt::Debug, + ::Err: std::fmt::Debug, +{ + let mut stack = Stack::new(same_precedence); + + loop { + line = line.trim(); + if line.is_empty() { + break; + } + if let Some(rem) = line.strip_prefix('+') { + stack.push_op(Op::Sum); + line = rem; + } else if let Some(rem) = line.strip_prefix('*') { + stack.push_op(Op::Mul); + line = rem; + } else if let Some(rem) = line.strip_prefix('(') { + stack.push_paren_left(); + line = rem; + } else if let Some(rem) = line.strip_prefix(')') { + stack.eval_top(true); + let item = stack.pop_item(); + stack.pop_paren_left(); + stack.push_item(item); + line = rem; + } else { + let num_end = line.find(|c: char| !c.is_ascii_digit()).unwrap_or(line.len()); + let num = line[..num_end].parse::().unwrap(); + stack.push_item(num); + line = &line[num_end..]; + } + stack.eval_top(false); + } + stack.eval_top(true); + let item = stack.pop_item(); + assert!(stack.is_empty()); + item +} + +fn main() { + println!("Sum of evaluated lines (simple math): {}", INPUT.lines().map(|l| parse::(l, true)).sum::()); + println!("Sum of evaluated lines (advanced math): {}", INPUT.lines().map(|l| parse::(l, false)).sum::()); +} diff --git a/src/bin/day19.rs b/src/bin/day19.rs index 4879473..e343e71 100644 --- a/src/bin/day19.rs +++ b/src/bin/day19.rs @@ -1,203 +1,203 @@ -use std::collections::{HashMap, HashSet}; -use std::sync::Arc; - -const INPUT: &str = include_str!("../../data/day19"); - -#[derive(Clone, Debug)] -enum Rule { - Char(char), - Chain(Vec), -} - -impl Rule { - fn parse_part(alt: &str) -> Self { - let alt = alt.trim(); - if let Some(alt) = alt.strip_prefix('"') { - let alt = alt.strip_suffix('"').unwrap(); - assert_eq!(alt.len(), 1); - Self::Char(alt.chars().next().unwrap()) - } else { - Self::Chain(alt.split_whitespace().map(|s| s.parse::().unwrap()).collect()) - } - } - - fn parse_line(line: &str) -> (u32, CachedRule) { - let colon = line.find(": ").unwrap(); - let id = line[..colon].parse::().unwrap(); - let alts = line[colon+2..].split(" | ").map(Self::parse_part).collect(); - (id, CachedRule::Alternatives(alts)) - } -} - -fn _make_combs(target: &mut HashSet, current: &mut String, list: &[Arc>]) { - if list.is_empty() { - target.insert(current.clone()); - } else { - let old_len = current.len(); - for s in &*list[0] { - current.push_str(s); - _make_combs(target, current, &list[1..]); - current.truncate(old_len); - } - } -} - -#[derive(Clone, Debug)] -enum CachedRule { - Alternatives(Vec), - CachedStrings(Arc>, HashSet), -} - -#[derive(Clone, Debug)] -struct Grammar { - expansions: HashMap, -} - -impl Grammar { - fn parse(rule_lines: &str) -> Self { - let expansions = rule_lines.lines().map(Rule::parse_line).collect(); - Self { expansions } - } - - fn _build_sets(&mut self, recursive_check: &mut HashSet, ndx: u32) -> Option>> { - let rules = self.expansions.get(&ndx).unwrap(); - let alts = match rules { - CachedRule::Alternatives(alts) => alts, - CachedRule::CachedStrings(cs, _) => return Some(cs.clone()), - }; - - if recursive_check.contains(&ndx) { - return None; - } - recursive_check.insert(ndx); - - let mut result = HashSet::::new(); - - let mut found_recursion = false; - for rule in alts.clone() { - match rule { - Rule::Char(c) => { - result.insert(c.to_string()); - }, - Rule::Chain(parts) => { - let mut comb = Vec::new(); - for part_ndx in parts { - // abort if nested lookup loops - if let Some(cs) = self._build_sets(recursive_check, part_ndx) { - comb.push(cs); - } else { - found_recursion = true; - } - } - if !found_recursion { - _make_combs(&mut result, &mut String::new(), &comb); - } - }, - } - } - if found_recursion { - return None; - } - let lengths: HashSet = result.iter().map(|s| s.len()).collect(); - let result = Arc::new(result); - recursive_check.remove(&ndx); - self.expansions.insert(ndx, CachedRule::CachedStrings(result.clone(), lengths)); - Some(result) - } - - fn optimize(&mut self) { - let mut recursive_check = HashSet::new(); - self._build_sets(&mut recursive_check, 0); - recursive_check.insert(0); - for id in recursive_check.clone() { - if let Some(CachedRule::Alternatives(alts)) = self.expansions.get(&id) { - for alt in alts { - if let Rule::Chain(chain) = alt { - recursive_check.extend(chain); - } - } - } - } - let rule_ids: HashSet = self.expansions.keys().cloned().collect(); - for id in rule_ids.difference(&recursive_check) { - self.expansions.remove(id); - } - } - - fn simple_match(&self, data: &str) -> bool { - match self.expansions.get(&0) { - Some(CachedRule::CachedStrings(cs, _)) => cs.contains(data), - _ => panic!("not simple enough"), - } - } - - fn _complex_match_chain<'data>(&self, chain: &[u32], data: &'data str) -> Vec<&'data str> { - if chain.is_empty() { - return vec![data]; - } - self._complex_match(chain[0], data).into_iter().map(|rem| { - self._complex_match_chain(&chain[1..], rem) - }).flatten().collect() - } - - fn _complex_match<'data>(&self, start: u32, data: &'data str) -> Vec<&'data str> { - let cr = self.expansions.get(&start).unwrap(); - match cr { - CachedRule::Alternatives(alts) => { - alts.iter().map(|rule| { - let chain = match rule { - Rule::Chain(chain) => chain, - _ => panic!("only chains here"), - }; - self._complex_match_chain(&chain, data) - }).flatten().collect() - }, - CachedRule::CachedStrings(cs, lens) => { - lens.iter().filter_map(|&len| { - if data.len() >= len && cs.contains(&data[..len]) { - Some(&data[len..]) - } else { - None - } - }).collect() - }, - } - } - - fn complex_match(&self, data: &str) -> bool { - self._complex_match(0, data).into_iter().any(str::is_empty) - } -} - -fn main() { - let input_grammar; - let data; - { - let split_pos = INPUT.find("\n\n").unwrap(); - input_grammar = Grammar::parse(&INPUT[..split_pos]); - data = INPUT[split_pos+2..].lines().map(str::to_string).collect::>(); - } - { - let mut grammar = input_grammar.clone(); - grammar.optimize(); - println!("Lines matching simple grammar: {}", data.iter().filter(|line| grammar.simple_match(line)).count()); - } - { - let mut grammar = input_grammar.clone(); - grammar.expansions.extend(vec![ - Rule::parse_line("8: 42 | 42 8"), - Rule::parse_line("11: 42 31 | 42 11 31"), - ]); - grammar.optimize(); -/* - println!("{:?}", grammar.expansions.keys()); - for (rule_id, rule) in grammar.expansions { - match rule { - CachedRule::Alternatives(alt) => println!("{} -> {:?}", rule_id, alt), - CachedRule::CachedStrings(_, lens) => println!("{} -> string with lengths: {:?}", rule_id, lens), - } - } -*/ - println!("Lines matching complex grammar: {}", data.iter().filter(|line| grammar.complex_match(line)).count()); - } -} +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; + +const INPUT: &str = include_str!("../../data/day19"); + +#[derive(Clone, Debug)] +enum Rule { + Char(char), + Chain(Vec), +} + +impl Rule { + fn parse_part(alt: &str) -> Self { + let alt = alt.trim(); + if let Some(alt) = alt.strip_prefix('"') { + let alt = alt.strip_suffix('"').unwrap(); + assert_eq!(alt.len(), 1); + Self::Char(alt.chars().next().unwrap()) + } else { + Self::Chain(alt.split_whitespace().map(|s| s.parse::().unwrap()).collect()) + } + } + + fn parse_line(line: &str) -> (u32, CachedRule) { + let colon = line.find(": ").unwrap(); + let id = line[..colon].parse::().unwrap(); + let alts = line[colon+2..].split(" | ").map(Self::parse_part).collect(); + (id, CachedRule::Alternatives(alts)) + } +} + +fn _make_combs(target: &mut HashSet, current: &mut String, list: &[Arc>]) { + if list.is_empty() { + target.insert(current.clone()); + } else { + let old_len = current.len(); + for s in &*list[0] { + current.push_str(s); + _make_combs(target, current, &list[1..]); + current.truncate(old_len); + } + } +} + +#[derive(Clone, Debug)] +enum CachedRule { + Alternatives(Vec), + CachedStrings(Arc>, HashSet), +} + +#[derive(Clone, Debug)] +struct Grammar { + expansions: HashMap, +} + +impl Grammar { + fn parse(rule_lines: &str) -> Self { + let expansions = rule_lines.lines().map(Rule::parse_line).collect(); + Self { expansions } + } + + fn _build_sets(&mut self, recursive_check: &mut HashSet, ndx: u32) -> Option>> { + let rules = self.expansions.get(&ndx).unwrap(); + let alts = match rules { + CachedRule::Alternatives(alts) => alts, + CachedRule::CachedStrings(cs, _) => return Some(cs.clone()), + }; + + if recursive_check.contains(&ndx) { + return None; + } + recursive_check.insert(ndx); + + let mut result = HashSet::::new(); + + let mut found_recursion = false; + for rule in alts.clone() { + match rule { + Rule::Char(c) => { + result.insert(c.to_string()); + }, + Rule::Chain(parts) => { + let mut comb = Vec::new(); + for part_ndx in parts { + // abort if nested lookup loops + if let Some(cs) = self._build_sets(recursive_check, part_ndx) { + comb.push(cs); + } else { + found_recursion = true; + } + } + if !found_recursion { + _make_combs(&mut result, &mut String::new(), &comb); + } + }, + } + } + if found_recursion { + return None; + } + let lengths: HashSet = result.iter().map(|s| s.len()).collect(); + let result = Arc::new(result); + recursive_check.remove(&ndx); + self.expansions.insert(ndx, CachedRule::CachedStrings(result.clone(), lengths)); + Some(result) + } + + fn optimize(&mut self) { + let mut recursive_check = HashSet::new(); + self._build_sets(&mut recursive_check, 0); + recursive_check.insert(0); + for id in recursive_check.clone() { + if let Some(CachedRule::Alternatives(alts)) = self.expansions.get(&id) { + for alt in alts { + if let Rule::Chain(chain) = alt { + recursive_check.extend(chain); + } + } + } + } + let rule_ids: HashSet = self.expansions.keys().cloned().collect(); + for id in rule_ids.difference(&recursive_check) { + self.expansions.remove(id); + } + } + + fn simple_match(&self, data: &str) -> bool { + match self.expansions.get(&0) { + Some(CachedRule::CachedStrings(cs, _)) => cs.contains(data), + _ => panic!("not simple enough"), + } + } + + fn _complex_match_chain<'data>(&self, chain: &[u32], data: &'data str) -> Vec<&'data str> { + if chain.is_empty() { + return vec![data]; + } + self._complex_match(chain[0], data).into_iter().map(|rem| { + self._complex_match_chain(&chain[1..], rem) + }).flatten().collect() + } + + fn _complex_match<'data>(&self, start: u32, data: &'data str) -> Vec<&'data str> { + let cr = self.expansions.get(&start).unwrap(); + match cr { + CachedRule::Alternatives(alts) => { + alts.iter().map(|rule| { + let chain = match rule { + Rule::Chain(chain) => chain, + _ => panic!("only chains here"), + }; + self._complex_match_chain(&chain, data) + }).flatten().collect() + }, + CachedRule::CachedStrings(cs, lens) => { + lens.iter().filter_map(|&len| { + if data.len() >= len && cs.contains(&data[..len]) { + Some(&data[len..]) + } else { + None + } + }).collect() + }, + } + } + + fn complex_match(&self, data: &str) -> bool { + self._complex_match(0, data).into_iter().any(str::is_empty) + } +} + +fn main() { + let input_grammar; + let data; + { + let split_pos = INPUT.find("\n\n").unwrap(); + input_grammar = Grammar::parse(&INPUT[..split_pos]); + data = INPUT[split_pos+2..].lines().map(str::to_string).collect::>(); + } + { + let mut grammar = input_grammar.clone(); + grammar.optimize(); + println!("Lines matching simple grammar: {}", data.iter().filter(|line| grammar.simple_match(line)).count()); + } + { + let mut grammar = input_grammar.clone(); + grammar.expansions.extend(vec![ + Rule::parse_line("8: 42 | 42 8"), + Rule::parse_line("11: 42 31 | 42 11 31"), + ]); + grammar.optimize(); +/* + println!("{:?}", grammar.expansions.keys()); + for (rule_id, rule) in grammar.expansions { + match rule { + CachedRule::Alternatives(alt) => println!("{} -> {:?}", rule_id, alt), + CachedRule::CachedStrings(_, lens) => println!("{} -> string with lengths: {:?}", rule_id, lens), + } + } +*/ + println!("Lines matching complex grammar: {}", data.iter().filter(|line| grammar.complex_match(line)).count()); + } +} diff --git a/src/bin/day20.rs b/src/bin/day20.rs index b527c02..7698839 100644 --- a/src/bin/day20.rs +++ b/src/bin/day20.rs @@ -1,466 +1,466 @@ -use std::collections::HashMap; - -const INPUT: &str = include_str!("../../data/day20"); - -#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] -enum Side { - Top, - Right, - Bottom, - Left, -} - -impl Side { - fn opposite(self) -> Self { - match self { - Side::Top => Side::Bottom, - Side::Right => Side::Left, - Side::Bottom => Side::Top, - Side::Left => Side::Right, - } - } - - fn transform(self, width: usize, flip: bool, row: usize, col: usize) -> (usize, usize) { - let (row, col) = if flip { - (row, width - col - 1) - } else { - (row, col) - }; - let (row, col) = match self { - Side::Top => (row, col), - Side::Right => (col, width - row - 1), - Side::Bottom => (width - row - 1, width - col - 1), - Side::Left => (width - col - 1, row), - }; - (row, col) - } -} - -#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] -struct Edge { - len: u32, - mask: u32, -} - -impl Edge { - fn new(bits: I) -> Self - where - I: IntoIterator, - { - let (len, mask) = bits.into_iter().fold((0u32, 0u32), |(len, mask), bit| { - (len+1, (mask << 1) + if bit { 1 } else { 0 }) - }); - Edge { len, mask } - } - - fn norm_dir(self) -> Self { - let rev_mask = self.mask.reverse_bits() >> (32 - self.len); - if self.mask < rev_mask { - Edge { len: self.len, mask: self.mask } - } else { - Edge { len: self.len, mask: rev_mask } - } - } -} - -impl std::fmt::Debug for Edge { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use std::fmt::Write; - f.write_char('"')?; - for ndx in 0..self.len { - let v = 0 != self.mask & (1 << ndx); - f.write_char(if v { '#' } else { '.' })?; - } - f.write_char('"') - } -} - -#[derive(Clone, Copy)] -struct Mask { - lines: [[bool; 20]; 3], -} - -impl Mask { - const EMPTY: Mask = Mask { lines: [[false; 20]; 3] }; - const DEFAULT: Mask = { - let mut lines = Self::EMPTY.lines; - lines[0][18] = true; - lines[1][0] = true; - lines[1][5] = true; - lines[1][6] = true; - lines[1][11] = true; - lines[1][12] = true; - lines[1][17] = true; - lines[1][18] = true; - lines[1][19] = true; - lines[2][1] = true; - lines[2][4] = true; - lines[2][7] = true; - lines[2][10] = true; - lines[2][13] = true; - lines[2][16] = true; - Self { lines } - }; - - fn _flip_y(self) -> Self { - let [a,b,c] = self.lines; - Self { lines: [c, b, a] } - } - - fn _flip_x(self) -> Self { - let [mut a, mut b, mut c] = self.lines; - a.reverse(); - b.reverse(); - c.reverse(); - Self { lines: [a, b, c] } - } - - fn all_masks() -> Vec { - let def = Self::DEFAULT; - let flip_x = def._flip_x(); - vec![ - def, - def._flip_y(), - flip_x, - flip_x._flip_y(), - ] - } - - fn matches_horizontal(&self, tile: &Tile, row: usize, col: usize) -> bool { - if row + 3 > tile.width || col + 20 > tile.width { - return false; - } - for y in 0..3 { - for x in 0..20 { - if self.lines[y][x] && !tile[(row+y, col+x)] { - return false; - } - } - } - true - } - - fn mark_horizontal(&self, tile: &mut Tile, row: usize, col: usize) { - for y in 0..3 { - for x in 0..20 { - if self.lines[y][x] { - tile[(row+y, col+x)] = true; - } - } - } - } - - fn matches_vertical(&self, tile: &Tile, row: usize, col: usize) -> bool { - if row + 20 > tile.width || col + 3 > tile.width { - return false; - } - for y in 0..3 { - for x in 0..20 { - if self.lines[y][x] && !tile[(row+x, col+y)] { - return false; - } - } - } - true - } - - fn mark_vertical(&self, tile: &mut Tile, row: usize, col: usize) { - for y in 0..3 { - for x in 0..20 { - if self.lines[y][x] { - tile[(row+x, col+y)] = true; - } - } - } - } -} - -impl std::fmt::Debug for Mask { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use std::fmt::Write; - f.write_char('\n')?; - for y in 0..3 { - for x in 0..20 { - f.write_char(if self.lines[y][x] { '#' } else { '.' })?; - } - f.write_char('\n')?; - } - Ok(()) - } -} - -#[derive(Clone, PartialEq, Eq, Hash)] -struct Tile { - width: usize, - data: Vec, -} - -impl Tile { - fn parse(block: &str) -> (u32, Self) { - let head_split = block.find(":\n").unwrap(); - let id = block[..head_split].strip_prefix("Tile ").unwrap().parse::().unwrap(); - let block = &block[head_split+2..]; - let first_line = block.lines().next().unwrap(); - let width = first_line.len(); - let data = block.lines() - .map(|l| { - assert_eq!(l.len(), width); - l.chars().map(|c| c == '#') - }) - .flatten().collect::>(); - assert_eq!(data.len(), width * width); - - (id, Self { width, data }) - } - - fn edge(&self, side: Side) -> Edge { - match side { - Side::Top => Edge::new(self.data[..self.width].iter().copied()), - Side::Bottom => Edge::new(self.data[self.width*(self.width-1)..].iter().copied()), - Side::Left => Edge::new((0..self.width).map(|n| self.data[n*self.width])), - Side::Right => Edge::new((0..self.width).map(|n| self.data[(n+1)*self.width-1])), - } - } - - // rotate/flip given previous sides to top and left - fn rotate(&self, top: Side, left: Side) -> Self { - let flip = match (top, left) { - (Side::Top, Side::Left) => false, - (Side::Top, Side::Right) => true, - (Side::Right, Side::Top) => false, - (Side::Right, Side::Bottom) => true, - (Side::Bottom, Side::Right) => false, - (Side::Bottom, Side::Left) => true, - (Side::Left, Side::Bottom) => false, - (Side::Left, Side::Top) => true, - _ => panic!("invalid rotation: {:?} -> Top and {:?} -> Left", top, left), - }; - let data = (0..self.width).map(|row| { - (0..self.width).map(move |col| { - let (row, col) = top.transform(self.width, flip, row, col); - self[(row, col)] - }) - }).flatten().collect(); - Self { width: self.width, data } - } - - fn find_monster(&self) -> Tile { - let mut marked = Self { width: self.width, data: self.data.iter().map(|_| false).collect() }; - let patterns = Mask::all_masks(); - for row in 0..self.width { - for col in 0..self.width { - for pattern in &patterns { - if pattern.matches_horizontal(self, row, col) { - pattern.mark_horizontal(&mut marked, row, col); - } - if pattern.matches_vertical(self, row, col) { - pattern.mark_vertical(&mut marked, row, col); - } - } - } - } - marked - } -} - -impl std::ops::Index<(usize, usize)> for Tile { - type Output = bool; - - fn index(&self, (row, col): (usize, usize)) -> &Self::Output { - assert!(col <= self.width); - &self.data[row * self.width + col] - } -} - -impl std::ops::IndexMut<(usize, usize)> for Tile { - fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output { - assert!(col <= self.width); - &mut self.data[row * self.width + col] - } -} - -impl std::fmt::Debug for Tile { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use std::fmt::Write; - f.write_char('\n')?; - for y in 0..self.width { - for x in 0..self.width { - f.write_char(if self[(y, x)] { '#' } else { '.' })?; - } - f.write_char('\n')?; - } - Ok(()) - } -} - -#[derive(Clone, PartialEq, Eq, Debug)] -struct TileDetailed { - tile: Tile, - edge_to_side: HashMap, - side_to_edge: HashMap, -} - -impl TileDetailed { - fn edge(&self, side: Side) -> Edge { - *self.side_to_edge.get(&side).unwrap() - } - - fn parse(block: &str) -> (u32, Self) { - let (id, tile) = Tile::parse(block); - - let edges = vec![ - (Side::Top, tile.edge(Side::Top).norm_dir()), - (Side::Right, tile.edge(Side::Right).norm_dir()), - (Side::Bottom, tile.edge(Side::Bottom).norm_dir()), - (Side::Left, tile.edge(Side::Left).norm_dir()), - ]; - let edge_to_side = edges.iter().map(|&(side, edge)| (edge, side)).collect(); - let side_to_edge = edges.into_iter().collect(); - - (id, Self { tile, edge_to_side, side_to_edge }) - } -} - -#[derive(Clone, Copy, Debug)] -struct TilePlacement { - tile: u32, - top_side: Side, - bottom_edge: Edge, - left_side: Side, - right_edge: Edge, -} - -fn main() { - let tiles: HashMap = INPUT.trim().split("\n\n").map(TileDetailed::parse).collect(); - let mut edges: HashMap> = HashMap::new(); - for (&id, tile) in &tiles { - for (&side, &edge) in &tile.side_to_edge { - edges.entry(edge).or_default().push((id, side)); - } - } - let single_edges = edges.iter().filter_map(|(_edge, id_sides)| { - if id_sides.len() == 1 { - Some(id_sides[0]) - } else { - assert!(id_sides.len() == 2); - None - } - }).collect::>(); - let mut single_edges_per_tile = HashMap::>::new(); - for &(tile_id, side) in &single_edges { - single_edges_per_tile.entry(tile_id).or_default().push(side); - } - let corners = single_edges_per_tile.iter().filter_map(|(&tile_id, sides)| { - if sides.len() == 2 { - Some(tile_id) - } else { - None - } - }).collect::>(); - let corners_prod = corners.iter().map(|&tile_id| tile_id as u64).product::(); - println!("Product of corner tile ids: {}", corners_prod); - - let mut grid_tiles: [[Option; 12]; 12] = [[None; 12]; 12]; - for row in 0..12 { - if row == 0 { - // orient top left tile - let tile: u32 = corners.iter().copied().min().unwrap(); - let top_side: Side = *single_edges_per_tile.get(&tile).unwrap().iter().min().unwrap(); - let bottom_edge = tiles.get(&tile).unwrap().edge(top_side.opposite()); - let left_side: Side = *single_edges_per_tile.get(&tile).unwrap().iter().max().unwrap(); - let right_edge = tiles.get(&tile).unwrap().edge(left_side.opposite()); - grid_tiles[0][0] = Some(TilePlacement { tile, top_side, bottom_edge, left_side, right_edge }); - } else { - // left column; look at tile above - let tile_top = grid_tiles[row-1][0].unwrap().tile; - let top_edge = grid_tiles[row-1][0].unwrap().bottom_edge; - let (tile, top_side) = edges.get(&top_edge).unwrap().iter().filter_map(|&(tile_id, side)| { - if tile_id != tile_top { - Some((tile_id, side)) - } else { - None - } - }).next().unwrap(); - let bottom_side = top_side.opposite(); - let left_side = *single_edges_per_tile.get(&tile).unwrap().iter().filter(|&&side| side != bottom_side).next().unwrap(); - assert_ne!(top_side, left_side); - assert_ne!(top_side, left_side.opposite()); - let bottom_edge = tiles.get(&tile).unwrap().edge(top_side.opposite()); - let right_edge = tiles.get(&tile).unwrap().edge(left_side.opposite()); - grid_tiles[row][0] = Some(TilePlacement { tile, top_side, bottom_edge, left_side, right_edge }); - } - // complete row - for col in 1..12 { - // look at tile to the left - let tile_left = grid_tiles[row][col-1].unwrap().tile; - let left_edge = grid_tiles[row][col-1].unwrap().right_edge; - let (tile, left_side) = edges.get(&left_edge).unwrap().iter().filter_map(|&(tile_id, side)| { - if tile_id != tile_left { - Some((tile_id, side)) - } else { - None - } - }).next().unwrap(); - let right_edge = tiles.get(&tile).unwrap().edge(left_side.opposite()); - let top_side; - if row == 0 { - if col == 11 { - // top right corner - let right_side = left_side.opposite(); - top_side = *single_edges_per_tile.get(&tile).unwrap().iter().filter(|&&side| side != right_side).next().unwrap(); - } else { - top_side = *single_edges_per_tile.get(&tile).unwrap().iter().next().unwrap(); - } - } else { - let tile_top = grid_tiles[row-1][col].unwrap().tile; - let top_edge = grid_tiles[row-1][col].unwrap().bottom_edge; - top_side = edges.get(&top_edge).unwrap().iter().filter_map(|&(tile_id, side)| { - if tile_id != tile_top { - assert_eq!(tile, tile_id); - Some(side) - } else { - None - } - }).next().unwrap(); - } - assert_ne!(left_side, top_side); - assert_ne!(left_side, top_side.opposite()); - let bottom_edge = tiles.get(&tile).unwrap().edge(top_side.opposite()); - grid_tiles[row][col] = Some(TilePlacement { tile, top_side, bottom_edge, left_side, right_edge }); - } - } - - let mut rotated_tiles = Vec::new(); - let mut picture = Tile { width: 12 * 8, data: Vec::new() }; - picture.data.resize(picture.width * picture.width, false); - for row in 0..12 { - rotated_tiles.push(Vec::new()); - for col in 0..12 { - let tp = grid_tiles[row][col].unwrap(); - let tile = tiles.get(&tp.tile).unwrap().tile.rotate(tp.top_side, tp.left_side); - rotated_tiles[row].push(tile.clone()); - assert_eq!(rotated_tiles[row][col], tile); - - // check edges - if row > 0 { - assert_eq!(rotated_tiles[row-1][col].edge(Side::Bottom), tile.edge(Side::Top)); - } - if col > 0 { - assert_eq!(rotated_tiles[row][col-1].edge(Side::Right), tile.edge(Side::Left)); - } - - // assemble full picture - for y in 0..tile.width-2 { - for x in 0..tile.width-2 { - picture[(row*8+y, col*8+x)] = tile[(y+1, x+1)]; - } - } - } - } - println!("{:?}", picture); - let monsters = picture.find_monster(); - println!("{:?}", monsters); - let sea_count = picture.data.iter().filter(|&&v| v).count(); - let monster_count = monsters.data.iter().filter(|&&v| v).count(); - println!("Roughness: {}", sea_count - monster_count); -} +use std::collections::HashMap; + +const INPUT: &str = include_str!("../../data/day20"); + +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] +enum Side { + Top, + Right, + Bottom, + Left, +} + +impl Side { + fn opposite(self) -> Self { + match self { + Side::Top => Side::Bottom, + Side::Right => Side::Left, + Side::Bottom => Side::Top, + Side::Left => Side::Right, + } + } + + fn transform(self, width: usize, flip: bool, row: usize, col: usize) -> (usize, usize) { + let (row, col) = if flip { + (row, width - col - 1) + } else { + (row, col) + }; + let (row, col) = match self { + Side::Top => (row, col), + Side::Right => (col, width - row - 1), + Side::Bottom => (width - row - 1, width - col - 1), + Side::Left => (width - col - 1, row), + }; + (row, col) + } +} + +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] +struct Edge { + len: u32, + mask: u32, +} + +impl Edge { + fn new(bits: I) -> Self + where + I: IntoIterator, + { + let (len, mask) = bits.into_iter().fold((0u32, 0u32), |(len, mask), bit| { + (len+1, (mask << 1) + if bit { 1 } else { 0 }) + }); + Edge { len, mask } + } + + fn norm_dir(self) -> Self { + let rev_mask = self.mask.reverse_bits() >> (32 - self.len); + if self.mask < rev_mask { + Edge { len: self.len, mask: self.mask } + } else { + Edge { len: self.len, mask: rev_mask } + } + } +} + +impl std::fmt::Debug for Edge { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::fmt::Write; + f.write_char('"')?; + for ndx in 0..self.len { + let v = 0 != self.mask & (1 << ndx); + f.write_char(if v { '#' } else { '.' })?; + } + f.write_char('"') + } +} + +#[derive(Clone, Copy)] +struct Mask { + lines: [[bool; 20]; 3], +} + +impl Mask { + const EMPTY: Mask = Mask { lines: [[false; 20]; 3] }; + const DEFAULT: Mask = { + let mut lines = Self::EMPTY.lines; + lines[0][18] = true; + lines[1][0] = true; + lines[1][5] = true; + lines[1][6] = true; + lines[1][11] = true; + lines[1][12] = true; + lines[1][17] = true; + lines[1][18] = true; + lines[1][19] = true; + lines[2][1] = true; + lines[2][4] = true; + lines[2][7] = true; + lines[2][10] = true; + lines[2][13] = true; + lines[2][16] = true; + Self { lines } + }; + + fn _flip_y(self) -> Self { + let [a,b,c] = self.lines; + Self { lines: [c, b, a] } + } + + fn _flip_x(self) -> Self { + let [mut a, mut b, mut c] = self.lines; + a.reverse(); + b.reverse(); + c.reverse(); + Self { lines: [a, b, c] } + } + + fn all_masks() -> Vec { + let def = Self::DEFAULT; + let flip_x = def._flip_x(); + vec![ + def, + def._flip_y(), + flip_x, + flip_x._flip_y(), + ] + } + + fn matches_horizontal(&self, tile: &Tile, row: usize, col: usize) -> bool { + if row + 3 > tile.width || col + 20 > tile.width { + return false; + } + for y in 0..3 { + for x in 0..20 { + if self.lines[y][x] && !tile[(row+y, col+x)] { + return false; + } + } + } + true + } + + fn mark_horizontal(&self, tile: &mut Tile, row: usize, col: usize) { + for y in 0..3 { + for x in 0..20 { + if self.lines[y][x] { + tile[(row+y, col+x)] = true; + } + } + } + } + + fn matches_vertical(&self, tile: &Tile, row: usize, col: usize) -> bool { + if row + 20 > tile.width || col + 3 > tile.width { + return false; + } + for y in 0..3 { + for x in 0..20 { + if self.lines[y][x] && !tile[(row+x, col+y)] { + return false; + } + } + } + true + } + + fn mark_vertical(&self, tile: &mut Tile, row: usize, col: usize) { + for y in 0..3 { + for x in 0..20 { + if self.lines[y][x] { + tile[(row+x, col+y)] = true; + } + } + } + } +} + +impl std::fmt::Debug for Mask { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::fmt::Write; + f.write_char('\n')?; + for y in 0..3 { + for x in 0..20 { + f.write_char(if self.lines[y][x] { '#' } else { '.' })?; + } + f.write_char('\n')?; + } + Ok(()) + } +} + +#[derive(Clone, PartialEq, Eq, Hash)] +struct Tile { + width: usize, + data: Vec, +} + +impl Tile { + fn parse(block: &str) -> (u32, Self) { + let head_split = block.find(":\n").unwrap(); + let id = block[..head_split].strip_prefix("Tile ").unwrap().parse::().unwrap(); + let block = &block[head_split+2..]; + let first_line = block.lines().next().unwrap(); + let width = first_line.len(); + let data = block.lines() + .map(|l| { + assert_eq!(l.len(), width); + l.chars().map(|c| c == '#') + }) + .flatten().collect::>(); + assert_eq!(data.len(), width * width); + + (id, Self { width, data }) + } + + fn edge(&self, side: Side) -> Edge { + match side { + Side::Top => Edge::new(self.data[..self.width].iter().copied()), + Side::Bottom => Edge::new(self.data[self.width*(self.width-1)..].iter().copied()), + Side::Left => Edge::new((0..self.width).map(|n| self.data[n*self.width])), + Side::Right => Edge::new((0..self.width).map(|n| self.data[(n+1)*self.width-1])), + } + } + + // rotate/flip given previous sides to top and left + fn rotate(&self, top: Side, left: Side) -> Self { + let flip = match (top, left) { + (Side::Top, Side::Left) => false, + (Side::Top, Side::Right) => true, + (Side::Right, Side::Top) => false, + (Side::Right, Side::Bottom) => true, + (Side::Bottom, Side::Right) => false, + (Side::Bottom, Side::Left) => true, + (Side::Left, Side::Bottom) => false, + (Side::Left, Side::Top) => true, + _ => panic!("invalid rotation: {:?} -> Top and {:?} -> Left", top, left), + }; + let data = (0..self.width).map(|row| { + (0..self.width).map(move |col| { + let (row, col) = top.transform(self.width, flip, row, col); + self[(row, col)] + }) + }).flatten().collect(); + Self { width: self.width, data } + } + + fn find_monster(&self) -> Tile { + let mut marked = Self { width: self.width, data: self.data.iter().map(|_| false).collect() }; + let patterns = Mask::all_masks(); + for row in 0..self.width { + for col in 0..self.width { + for pattern in &patterns { + if pattern.matches_horizontal(self, row, col) { + pattern.mark_horizontal(&mut marked, row, col); + } + if pattern.matches_vertical(self, row, col) { + pattern.mark_vertical(&mut marked, row, col); + } + } + } + } + marked + } +} + +impl std::ops::Index<(usize, usize)> for Tile { + type Output = bool; + + fn index(&self, (row, col): (usize, usize)) -> &Self::Output { + assert!(col <= self.width); + &self.data[row * self.width + col] + } +} + +impl std::ops::IndexMut<(usize, usize)> for Tile { + fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output { + assert!(col <= self.width); + &mut self.data[row * self.width + col] + } +} + +impl std::fmt::Debug for Tile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::fmt::Write; + f.write_char('\n')?; + for y in 0..self.width { + for x in 0..self.width { + f.write_char(if self[(y, x)] { '#' } else { '.' })?; + } + f.write_char('\n')?; + } + Ok(()) + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +struct TileDetailed { + tile: Tile, + edge_to_side: HashMap, + side_to_edge: HashMap, +} + +impl TileDetailed { + fn edge(&self, side: Side) -> Edge { + *self.side_to_edge.get(&side).unwrap() + } + + fn parse(block: &str) -> (u32, Self) { + let (id, tile) = Tile::parse(block); + + let edges = vec![ + (Side::Top, tile.edge(Side::Top).norm_dir()), + (Side::Right, tile.edge(Side::Right).norm_dir()), + (Side::Bottom, tile.edge(Side::Bottom).norm_dir()), + (Side::Left, tile.edge(Side::Left).norm_dir()), + ]; + let edge_to_side = edges.iter().map(|&(side, edge)| (edge, side)).collect(); + let side_to_edge = edges.into_iter().collect(); + + (id, Self { tile, edge_to_side, side_to_edge }) + } +} + +#[derive(Clone, Copy, Debug)] +struct TilePlacement { + tile: u32, + top_side: Side, + bottom_edge: Edge, + left_side: Side, + right_edge: Edge, +} + +fn main() { + let tiles: HashMap = INPUT.trim().split("\n\n").map(TileDetailed::parse).collect(); + let mut edges: HashMap> = HashMap::new(); + for (&id, tile) in &tiles { + for (&side, &edge) in &tile.side_to_edge { + edges.entry(edge).or_default().push((id, side)); + } + } + let single_edges = edges.iter().filter_map(|(_edge, id_sides)| { + if id_sides.len() == 1 { + Some(id_sides[0]) + } else { + assert!(id_sides.len() == 2); + None + } + }).collect::>(); + let mut single_edges_per_tile = HashMap::>::new(); + for &(tile_id, side) in &single_edges { + single_edges_per_tile.entry(tile_id).or_default().push(side); + } + let corners = single_edges_per_tile.iter().filter_map(|(&tile_id, sides)| { + if sides.len() == 2 { + Some(tile_id) + } else { + None + } + }).collect::>(); + let corners_prod = corners.iter().map(|&tile_id| tile_id as u64).product::(); + println!("Product of corner tile ids: {}", corners_prod); + + let mut grid_tiles: [[Option; 12]; 12] = [[None; 12]; 12]; + for row in 0..12 { + if row == 0 { + // orient top left tile + let tile: u32 = corners.iter().copied().min().unwrap(); + let top_side: Side = *single_edges_per_tile.get(&tile).unwrap().iter().min().unwrap(); + let bottom_edge = tiles.get(&tile).unwrap().edge(top_side.opposite()); + let left_side: Side = *single_edges_per_tile.get(&tile).unwrap().iter().max().unwrap(); + let right_edge = tiles.get(&tile).unwrap().edge(left_side.opposite()); + grid_tiles[0][0] = Some(TilePlacement { tile, top_side, bottom_edge, left_side, right_edge }); + } else { + // left column; look at tile above + let tile_top = grid_tiles[row-1][0].unwrap().tile; + let top_edge = grid_tiles[row-1][0].unwrap().bottom_edge; + let (tile, top_side) = edges.get(&top_edge).unwrap().iter().filter_map(|&(tile_id, side)| { + if tile_id != tile_top { + Some((tile_id, side)) + } else { + None + } + }).next().unwrap(); + let bottom_side = top_side.opposite(); + let left_side = *single_edges_per_tile.get(&tile).unwrap().iter().filter(|&&side| side != bottom_side).next().unwrap(); + assert_ne!(top_side, left_side); + assert_ne!(top_side, left_side.opposite()); + let bottom_edge = tiles.get(&tile).unwrap().edge(top_side.opposite()); + let right_edge = tiles.get(&tile).unwrap().edge(left_side.opposite()); + grid_tiles[row][0] = Some(TilePlacement { tile, top_side, bottom_edge, left_side, right_edge }); + } + // complete row + for col in 1..12 { + // look at tile to the left + let tile_left = grid_tiles[row][col-1].unwrap().tile; + let left_edge = grid_tiles[row][col-1].unwrap().right_edge; + let (tile, left_side) = edges.get(&left_edge).unwrap().iter().filter_map(|&(tile_id, side)| { + if tile_id != tile_left { + Some((tile_id, side)) + } else { + None + } + }).next().unwrap(); + let right_edge = tiles.get(&tile).unwrap().edge(left_side.opposite()); + let top_side; + if row == 0 { + if col == 11 { + // top right corner + let right_side = left_side.opposite(); + top_side = *single_edges_per_tile.get(&tile).unwrap().iter().filter(|&&side| side != right_side).next().unwrap(); + } else { + top_side = *single_edges_per_tile.get(&tile).unwrap().iter().next().unwrap(); + } + } else { + let tile_top = grid_tiles[row-1][col].unwrap().tile; + let top_edge = grid_tiles[row-1][col].unwrap().bottom_edge; + top_side = edges.get(&top_edge).unwrap().iter().filter_map(|&(tile_id, side)| { + if tile_id != tile_top { + assert_eq!(tile, tile_id); + Some(side) + } else { + None + } + }).next().unwrap(); + } + assert_ne!(left_side, top_side); + assert_ne!(left_side, top_side.opposite()); + let bottom_edge = tiles.get(&tile).unwrap().edge(top_side.opposite()); + grid_tiles[row][col] = Some(TilePlacement { tile, top_side, bottom_edge, left_side, right_edge }); + } + } + + let mut rotated_tiles = Vec::new(); + let mut picture = Tile { width: 12 * 8, data: Vec::new() }; + picture.data.resize(picture.width * picture.width, false); + for row in 0..12 { + rotated_tiles.push(Vec::new()); + for col in 0..12 { + let tp = grid_tiles[row][col].unwrap(); + let tile = tiles.get(&tp.tile).unwrap().tile.rotate(tp.top_side, tp.left_side); + rotated_tiles[row].push(tile.clone()); + assert_eq!(rotated_tiles[row][col], tile); + + // check edges + if row > 0 { + assert_eq!(rotated_tiles[row-1][col].edge(Side::Bottom), tile.edge(Side::Top)); + } + if col > 0 { + assert_eq!(rotated_tiles[row][col-1].edge(Side::Right), tile.edge(Side::Left)); + } + + // assemble full picture + for y in 0..tile.width-2 { + for x in 0..tile.width-2 { + picture[(row*8+y, col*8+x)] = tile[(y+1, x+1)]; + } + } + } + } + println!("{:?}", picture); + let monsters = picture.find_monster(); + println!("{:?}", monsters); + let sea_count = picture.data.iter().filter(|&&v| v).count(); + let monster_count = monsters.data.iter().filter(|&&v| v).count(); + println!("Roughness: {}", sea_count - monster_count); +} diff --git a/src/bin/day21.rs b/src/bin/day21.rs index 3d4cd87..7409589 100644 --- a/src/bin/day21.rs +++ b/src/bin/day21.rs @@ -1,57 +1,57 @@ -use std::collections::{BTreeMap, HashMap, HashSet}; - -const INPUT: &str = include_str!("../../data/day21"); - -struct FoodDescription<'a> { - ingredients: HashSet<&'a str>, - allergens: HashSet<&'a str>, -} - -impl<'a> FoodDescription<'a> { - fn parse(line: &'a str) -> Self { - let pos = line.find(" (contains ").unwrap(); - let ingredients = &line[..pos]; - let allergens = line[pos..].strip_prefix(" (contains ").unwrap().strip_suffix(")").unwrap(); - FoodDescription { - ingredients: ingredients.split_whitespace().collect(), - allergens: allergens.split(", ").collect(), - } - } -} - -fn main() { - let fds = INPUT.lines().map(FoodDescription::parse).collect::>(); - let allergens: HashSet<&str> = fds.iter().map(|fd| fd.allergens.iter().cloned()).flatten().collect(); - let mut allergen_map_poss: HashMap<&str, HashSet<&str>> = allergens.iter().map(|&a| { - let mut is = fds.iter() - .filter(|fd| fd.allergens.contains(a)) - .map(|fd| &fd.ingredients); - let first_ingr: HashSet<&str> = is.next().unwrap().clone(); - let poss = is.fold(first_ingr, |memo, ingr| { - memo.intersection(ingr).cloned().collect::>() - }); - (a, poss) - }).collect(); - let mut allergen_map: BTreeMap<&str, &str> = BTreeMap::new(); - while !allergen_map_poss.is_empty() { - let (next_allg, next_ingr) = allergen_map_poss.iter().filter_map(|(&a, is)| { - if is.len() == 1 { - Some((a, *is.iter().next().unwrap())) - } else { - None - } - }).next().unwrap(); - allergen_map.insert(next_allg, next_ingr); - allergen_map_poss.remove(next_allg); - for ingr in allergen_map_poss.values_mut() { - ingr.remove(next_ingr); - } - } - println!("Allergens contained by ingredients: {:?}", allergen_map); - let all_ingredients: HashSet<&str> = allergen_map.values().cloned().collect(); - println!( - "Ingredients not allergens used {} times", - fds.iter().map(|fd| fd.ingredients.difference(&all_ingredients).count()).sum::(), - ); - println!("Dangerous ingredients: {}", allergen_map.values().cloned().collect::>().join(",")); -} +use std::collections::{BTreeMap, HashMap, HashSet}; + +const INPUT: &str = include_str!("../../data/day21"); + +struct FoodDescription<'a> { + ingredients: HashSet<&'a str>, + allergens: HashSet<&'a str>, +} + +impl<'a> FoodDescription<'a> { + fn parse(line: &'a str) -> Self { + let pos = line.find(" (contains ").unwrap(); + let ingredients = &line[..pos]; + let allergens = line[pos..].strip_prefix(" (contains ").unwrap().strip_suffix(")").unwrap(); + FoodDescription { + ingredients: ingredients.split_whitespace().collect(), + allergens: allergens.split(", ").collect(), + } + } +} + +fn main() { + let fds = INPUT.lines().map(FoodDescription::parse).collect::>(); + let allergens: HashSet<&str> = fds.iter().map(|fd| fd.allergens.iter().cloned()).flatten().collect(); + let mut allergen_map_poss: HashMap<&str, HashSet<&str>> = allergens.iter().map(|&a| { + let mut is = fds.iter() + .filter(|fd| fd.allergens.contains(a)) + .map(|fd| &fd.ingredients); + let first_ingr: HashSet<&str> = is.next().unwrap().clone(); + let poss = is.fold(first_ingr, |memo, ingr| { + memo.intersection(ingr).cloned().collect::>() + }); + (a, poss) + }).collect(); + let mut allergen_map: BTreeMap<&str, &str> = BTreeMap::new(); + while !allergen_map_poss.is_empty() { + let (next_allg, next_ingr) = allergen_map_poss.iter().filter_map(|(&a, is)| { + if is.len() == 1 { + Some((a, *is.iter().next().unwrap())) + } else { + None + } + }).next().unwrap(); + allergen_map.insert(next_allg, next_ingr); + allergen_map_poss.remove(next_allg); + for ingr in allergen_map_poss.values_mut() { + ingr.remove(next_ingr); + } + } + println!("Allergens contained by ingredients: {:?}", allergen_map); + let all_ingredients: HashSet<&str> = allergen_map.values().cloned().collect(); + println!( + "Ingredients not allergens used {} times", + fds.iter().map(|fd| fd.ingredients.difference(&all_ingredients).count()).sum::(), + ); + println!("Dangerous ingredients: {}", allergen_map.values().cloned().collect::>().join(",")); +} diff --git a/src/bin/day22.rs b/src/bin/day22.rs index 14413eb..f93ae42 100644 --- a/src/bin/day22.rs +++ b/src/bin/day22.rs @@ -1,98 +1,98 @@ -use std::collections::{VecDeque, HashSet}; - -type Deck = VecDeque; - -const INPUT: &str = include_str!("../../data/day22"); - -fn parse_player(block: &str) -> (u32, Deck) { - let block = block.strip_prefix("Player ").unwrap(); - let player = block[..1].parse::().unwrap(); - let block = block[1..].strip_prefix(":").unwrap().trim(); - let deck = block.lines().map(|s| s.parse::().unwrap()).collect(); - (player, deck) -} - -fn score(deck: &Deck) -> u64 { - deck.iter().rev().enumerate().map(|(pos, &value)| { - ((pos + 1) as u64) * (value as u64) - }).sum::() -} - -fn crab(mut deck1: Deck, mut deck2: Deck) { - while !deck1.is_empty() && !deck2.is_empty() { - let c1 = deck1.pop_front().unwrap(); - let c2 = deck2.pop_front().unwrap(); - if c1 > c2 { - deck1.push_back(c1); - deck1.push_back(c2); - } else { - assert!(c2 > c1); - deck2.push_back(c2); - deck2.push_back(c1); - } - } - if deck2.is_empty() { - println!("Player 1 won: {:?}", deck1); - println!("Score: {}", score(&deck1)); - } else { - println!("Player 2 won: {:?}", deck2); - println!("Score: {}", score(&deck2)); - } -} - -fn _crab_rec(deck1: &mut Deck, deck2: &mut Deck) -> bool { - // println!("Rec Combat: {:?}, {:?}", deck1, deck2); - let mut previous_rounds = HashSet::new(); - while !deck1.is_empty() && !deck2.is_empty() { - if !previous_rounds.insert((deck1.clone(), deck2.clone())) { - // prevent infinite loop; player 1 wins - return true; - } - let c1 = deck1.pop_front().unwrap(); - let c2 = deck2.pop_front().unwrap(); - let p1_won = if deck1.len() >= c1 as usize && deck2.len() >= c2 as usize { - let mut deck1: Deck = deck1.iter().copied().take(c1 as usize).collect(); - let mut deck2: Deck = deck2.iter().copied().take(c2 as usize).collect(); - assert_eq!(deck1.len(), c1 as usize); - assert_eq!(deck2.len(), c2 as usize); - // recurse - _crab_rec(&mut deck1, &mut deck2) - } else { - assert_ne!(c1, c2); - c1 > c2 - }; - if p1_won { - deck1.push_back(c1); - deck1.push_back(c2); - } else { - deck2.push_back(c2); - deck2.push_back(c1); - } - } - deck2.is_empty() -} - -fn crab_rec(mut deck1: Deck, mut deck2: Deck) { - let p1_won = _crab_rec(&mut deck1, &mut deck2); - if p1_won { - println!("Player 1 won recursive combat: {:?}", deck1); - println!("Score: {}", score(&deck1)); - } else { - println!("Player 2 won recursive combat: {:?}", deck2); - println!("Score: {}", score(&deck2)); - } -} - -fn main() { - let (deck1, deck2) = { - let pos = INPUT.find("\n\n").unwrap(); - let (id1, deck1) = parse_player(&INPUT[..pos]); - let (id2, deck2) = parse_player(&INPUT[pos+2..]); - assert_eq!(id1, 1); - assert_eq!(id2, 2); - (deck1, deck2) - }; - - crab(deck1.clone(), deck2.clone()); - crab_rec(deck1, deck2); -} +use std::collections::{VecDeque, HashSet}; + +type Deck = VecDeque; + +const INPUT: &str = include_str!("../../data/day22"); + +fn parse_player(block: &str) -> (u32, Deck) { + let block = block.strip_prefix("Player ").unwrap(); + let player = block[..1].parse::().unwrap(); + let block = block[1..].strip_prefix(":").unwrap().trim(); + let deck = block.lines().map(|s| s.parse::().unwrap()).collect(); + (player, deck) +} + +fn score(deck: &Deck) -> u64 { + deck.iter().rev().enumerate().map(|(pos, &value)| { + ((pos + 1) as u64) * (value as u64) + }).sum::() +} + +fn crab(mut deck1: Deck, mut deck2: Deck) { + while !deck1.is_empty() && !deck2.is_empty() { + let c1 = deck1.pop_front().unwrap(); + let c2 = deck2.pop_front().unwrap(); + if c1 > c2 { + deck1.push_back(c1); + deck1.push_back(c2); + } else { + assert!(c2 > c1); + deck2.push_back(c2); + deck2.push_back(c1); + } + } + if deck2.is_empty() { + println!("Player 1 won: {:?}", deck1); + println!("Score: {}", score(&deck1)); + } else { + println!("Player 2 won: {:?}", deck2); + println!("Score: {}", score(&deck2)); + } +} + +fn _crab_rec(deck1: &mut Deck, deck2: &mut Deck) -> bool { + // println!("Rec Combat: {:?}, {:?}", deck1, deck2); + let mut previous_rounds = HashSet::new(); + while !deck1.is_empty() && !deck2.is_empty() { + if !previous_rounds.insert((deck1.clone(), deck2.clone())) { + // prevent infinite loop; player 1 wins + return true; + } + let c1 = deck1.pop_front().unwrap(); + let c2 = deck2.pop_front().unwrap(); + let p1_won = if deck1.len() >= c1 as usize && deck2.len() >= c2 as usize { + let mut deck1: Deck = deck1.iter().copied().take(c1 as usize).collect(); + let mut deck2: Deck = deck2.iter().copied().take(c2 as usize).collect(); + assert_eq!(deck1.len(), c1 as usize); + assert_eq!(deck2.len(), c2 as usize); + // recurse + _crab_rec(&mut deck1, &mut deck2) + } else { + assert_ne!(c1, c2); + c1 > c2 + }; + if p1_won { + deck1.push_back(c1); + deck1.push_back(c2); + } else { + deck2.push_back(c2); + deck2.push_back(c1); + } + } + deck2.is_empty() +} + +fn crab_rec(mut deck1: Deck, mut deck2: Deck) { + let p1_won = _crab_rec(&mut deck1, &mut deck2); + if p1_won { + println!("Player 1 won recursive combat: {:?}", deck1); + println!("Score: {}", score(&deck1)); + } else { + println!("Player 2 won recursive combat: {:?}", deck2); + println!("Score: {}", score(&deck2)); + } +} + +fn main() { + let (deck1, deck2) = { + let pos = INPUT.find("\n\n").unwrap(); + let (id1, deck1) = parse_player(&INPUT[..pos]); + let (id2, deck2) = parse_player(&INPUT[pos+2..]); + assert_eq!(id1, 1); + assert_eq!(id2, 2); + (deck1, deck2) + }; + + crab(deck1.clone(), deck2.clone()); + crab_rec(deck1, deck2); +} diff --git a/src/bin/day23.rs b/src/bin/day23.rs index 33e2ae2..dadfe7c 100644 --- a/src/bin/day23.rs +++ b/src/bin/day23.rs @@ -1,217 +1,217 @@ -use std::mem::replace; - -const INPUT: &str = include_str!("../../data/day23"); - -type Item = usize; - -const MARK_MISSING: usize = !0; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -struct OptionItem(Item); - -impl OptionItem { - pub const NONE: Self = Self(MARK_MISSING); - - fn item(self) -> Option { - if self.0 == MARK_MISSING { - None - } else { - Some(self.0) - } - } -} - -impl From for OptionItem { - fn from(i: Item) -> Self { - assert_ne!(i, MARK_MISSING); - Self(i) - } -} - -impl From> for OptionItem { - fn from(i: Option) -> Self { - match i { - None => Self(MARK_MISSING), - Some(i) => Self::from(i), - } - } -} - -#[derive(Clone, PartialEq, Eq, Debug)] -struct Cups { - current: OptionItem, - last: OptionItem, - next_cup: Vec, -} - -impl Default for Cups { - fn default() -> Self { - Self { - current: OptionItem::NONE, - last: OptionItem::NONE, - next_cup: Vec::new(), - } - } -} - -impl Cups { - fn last(&self) -> Option { - self.last.item() - } - - fn insert_after(&mut self, prev: Item, value: Item) { - let next = replace(&mut self.next_cup[prev], value.into()).item().unwrap(); - if value >= self.next_cup.len() { - self.next_cup.resize(value, OptionItem::NONE); - self.next_cup.push(next.into()); - assert_eq!(self.next_cup[value], next.into()); - } else { - assert_eq!(self.next_cup[value], OptionItem::NONE); - self.next_cup[value] = next.into(); - } - if self.last.item().unwrap() == prev { - self.last = value.into(); - } - } - - fn extend_after(&mut self, mut after: Item, iter: I) - where - I: IntoIterator, - { - for item in iter { - self.insert_after(after, item); - after = item; - } - } - - fn extend(&mut self, iter: I) - where - I: IntoIterator, - { - let mut iter = iter.into_iter(); - if let Some(first) = iter.next() { - if let Some(prev) = self.last() { - self.insert_after(prev, first); - } else { - assert!(self.next_cup.is_empty()); - self.next_cup.resize(first, OptionItem::NONE); - self.next_cup.push(first.into()); - self.current = first.into(); - self.last = first.into(); - } - self.extend_after(first, iter); - } - } - - fn iter_from(&self, start: Item) -> impl Iterator + '_ { - let mut current = start; - let mut first = true; - std::iter::from_fn(move || { - if !first && current == start { - return None; - } - first = false; - let item = current; - current = self.next_cup[item].item().unwrap(); - Some(item) - }) - } - - fn iter(&self) -> impl Iterator + '_ { - self.iter_from(self.current.item().unwrap()) - } - - fn remove_after(&mut self, previous: Item) -> Item { - let item = self.next_cup[previous].item().unwrap(); - let follow = self.next_cup[item].item().unwrap(); - self.next_cup[item] = OptionItem::NONE; - self.next_cup[previous] = follow.into(); - if item == self.current.item().unwrap() { - if item == previous { - // last item - self.current = OptionItem::NONE; - self.last = OptionItem::NONE; - self.next_cup.clear(); - } else { - // if we removed the "current" item: move current pointer forward - self.current = follow.into(); - } - } else if item == self.last.item().unwrap() { - // just removed the last item - self.last = previous.into(); - } - item - } - - fn drain_after(&mut self, previous: Item, mut len: usize) -> impl Iterator + '_ { - std::iter::from_fn(move || { - if len == 0 { - return None; - } - len -= 1; - Some(self.remove_after(previous)) - }) - } - - fn drain(&mut self, range: std::ops::Range) -> impl Iterator + '_ { - let len = range.end - range.start; - let after = if len == 0 { - 0 // don't care - } else if range.start > 0 { - self.iter().skip(range.start - 1).next().unwrap() - } else { - self.last().unwrap() - }; - self.drain_after(after, len) - } - - fn parse(cups: &str) -> Self { - let mut result = Cups::default(); - result.extend(cups.bytes().map(|c| (c - b'0') as Item)); - result - } - - fn rotate_left(&mut self, items: usize) { - let next = self.iter().skip(items).next().unwrap(); - self.current = next.into(); - } - - fn mix(&mut self) { - let max_entry = self.next_cup.len() - 1; - let pickup = self.drain(1..4).collect::>(); - let mut insert_after = self.current.item().unwrap(); - loop { - insert_after = match insert_after.checked_sub(1) { - Some(n) => n, - None => max_entry, - }; - if self.next_cup[insert_after] != OptionItem::NONE { - break; - } - } - self.extend_after(insert_after, pickup); - self.rotate_left(1); - } - - fn rotate_to_one(&mut self) { - assert_ne!(self.next_cup[1], OptionItem::NONE); - self.current = 1.into(); - } -} - -fn main() { - let mut cups = Cups::parse(INPUT.trim()); - for _ in 0..100 { - cups.mix(); - } - cups.rotate_to_one(); - println!("Cups: {}", cups.iter().skip(1).map(|c| format!("{}", c)).collect::>().join("")); - - let mut cups = Cups::parse(INPUT.trim()); - cups.extend(10..=1000000); - for _ in 0..10000000 { - cups.mix() - } - cups.rotate_to_one(); - println!("Cup product: {}", cups.drain(1..3).map(|c| c as u64).product::()); -} +use std::mem::replace; + +const INPUT: &str = include_str!("../../data/day23"); + +type Item = usize; + +const MARK_MISSING: usize = !0; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +struct OptionItem(Item); + +impl OptionItem { + pub const NONE: Self = Self(MARK_MISSING); + + fn item(self) -> Option { + if self.0 == MARK_MISSING { + None + } else { + Some(self.0) + } + } +} + +impl From for OptionItem { + fn from(i: Item) -> Self { + assert_ne!(i, MARK_MISSING); + Self(i) + } +} + +impl From> for OptionItem { + fn from(i: Option) -> Self { + match i { + None => Self(MARK_MISSING), + Some(i) => Self::from(i), + } + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +struct Cups { + current: OptionItem, + last: OptionItem, + next_cup: Vec, +} + +impl Default for Cups { + fn default() -> Self { + Self { + current: OptionItem::NONE, + last: OptionItem::NONE, + next_cup: Vec::new(), + } + } +} + +impl Cups { + fn last(&self) -> Option { + self.last.item() + } + + fn insert_after(&mut self, prev: Item, value: Item) { + let next = replace(&mut self.next_cup[prev], value.into()).item().unwrap(); + if value >= self.next_cup.len() { + self.next_cup.resize(value, OptionItem::NONE); + self.next_cup.push(next.into()); + assert_eq!(self.next_cup[value], next.into()); + } else { + assert_eq!(self.next_cup[value], OptionItem::NONE); + self.next_cup[value] = next.into(); + } + if self.last.item().unwrap() == prev { + self.last = value.into(); + } + } + + fn extend_after(&mut self, mut after: Item, iter: I) + where + I: IntoIterator, + { + for item in iter { + self.insert_after(after, item); + after = item; + } + } + + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + let mut iter = iter.into_iter(); + if let Some(first) = iter.next() { + if let Some(prev) = self.last() { + self.insert_after(prev, first); + } else { + assert!(self.next_cup.is_empty()); + self.next_cup.resize(first, OptionItem::NONE); + self.next_cup.push(first.into()); + self.current = first.into(); + self.last = first.into(); + } + self.extend_after(first, iter); + } + } + + fn iter_from(&self, start: Item) -> impl Iterator + '_ { + let mut current = start; + let mut first = true; + std::iter::from_fn(move || { + if !first && current == start { + return None; + } + first = false; + let item = current; + current = self.next_cup[item].item().unwrap(); + Some(item) + }) + } + + fn iter(&self) -> impl Iterator + '_ { + self.iter_from(self.current.item().unwrap()) + } + + fn remove_after(&mut self, previous: Item) -> Item { + let item = self.next_cup[previous].item().unwrap(); + let follow = self.next_cup[item].item().unwrap(); + self.next_cup[item] = OptionItem::NONE; + self.next_cup[previous] = follow.into(); + if item == self.current.item().unwrap() { + if item == previous { + // last item + self.current = OptionItem::NONE; + self.last = OptionItem::NONE; + self.next_cup.clear(); + } else { + // if we removed the "current" item: move current pointer forward + self.current = follow.into(); + } + } else if item == self.last.item().unwrap() { + // just removed the last item + self.last = previous.into(); + } + item + } + + fn drain_after(&mut self, previous: Item, mut len: usize) -> impl Iterator + '_ { + std::iter::from_fn(move || { + if len == 0 { + return None; + } + len -= 1; + Some(self.remove_after(previous)) + }) + } + + fn drain(&mut self, range: std::ops::Range) -> impl Iterator + '_ { + let len = range.end - range.start; + let after = if len == 0 { + 0 // don't care + } else if range.start > 0 { + self.iter().skip(range.start - 1).next().unwrap() + } else { + self.last().unwrap() + }; + self.drain_after(after, len) + } + + fn parse(cups: &str) -> Self { + let mut result = Cups::default(); + result.extend(cups.bytes().map(|c| (c - b'0') as Item)); + result + } + + fn rotate_left(&mut self, items: usize) { + let next = self.iter().skip(items).next().unwrap(); + self.current = next.into(); + } + + fn mix(&mut self) { + let max_entry = self.next_cup.len() - 1; + let pickup = self.drain(1..4).collect::>(); + let mut insert_after = self.current.item().unwrap(); + loop { + insert_after = match insert_after.checked_sub(1) { + Some(n) => n, + None => max_entry, + }; + if self.next_cup[insert_after] != OptionItem::NONE { + break; + } + } + self.extend_after(insert_after, pickup); + self.rotate_left(1); + } + + fn rotate_to_one(&mut self) { + assert_ne!(self.next_cup[1], OptionItem::NONE); + self.current = 1.into(); + } +} + +fn main() { + let mut cups = Cups::parse(INPUT.trim()); + for _ in 0..100 { + cups.mix(); + } + cups.rotate_to_one(); + println!("Cups: {}", cups.iter().skip(1).map(|c| format!("{}", c)).collect::>().join("")); + + let mut cups = Cups::parse(INPUT.trim()); + cups.extend(10..=1000000); + for _ in 0..10000000 { + cups.mix() + } + cups.rotate_to_one(); + println!("Cup product: {}", cups.drain(1..3).map(|c| c as u64).product::()); +} diff --git a/src/bin/day24.rs b/src/bin/day24.rs index e357778..dcca5b5 100644 --- a/src/bin/day24.rs +++ b/src/bin/day24.rs @@ -1,140 +1,140 @@ -use std::collections::HashSet; - -const INPUT: &str = include_str!("../../data/day24"); - -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -enum Direction { - East, - SouthEast, - SouthWest, - West, - NorthWest, - NorthEast, -} - -impl Direction { - const ALL: [Direction; 6] = [ - Self::East, - Self::SouthEast, - Self::SouthWest, - Self::West, - Self::NorthWest, - Self::NorthEast, - ]; - - fn symbol(self) -> &'static str { - match self { - Self::East => "e", - Self::SouthEast => "se", - Self::SouthWest => "sw", - Self::West => "w", - Self::NorthWest => "nw", - Self::NorthEast => "ne", - } - } - - fn parse_list(line: &str) -> Vec { - let mut line = line.trim(); - let mut result = Vec::new(); - 'outer: while !line.is_empty() { - for &d in Self::ALL.iter() { - if let Some(rem) = line.strip_prefix(d.symbol()) { - result.push(d); - line = rem; - continue 'outer; - } - } - panic!("Invalid directions: {:?}", line); - } - result - } - - // -> (row, col) - fn as_offset(self) -> (i32, i32) { - match self { - Self::East => (0, 1), - Self::SouthEast => (1, 1), - Self::SouthWest => (1, 0), - Self::West => (0, -1), - Self::NorthWest => (-1, -1), - Self::NorthEast => (-1, 0), - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Debug)] -struct Position { - row: i32, - // the "0"-column goes from south-west to north-east through (0/0) - col: i32, -} - -impl Position { - fn adjacent(self) -> impl Iterator { - Direction::ALL.iter().map(move |&d| self + d) - } -} - -impl std::ops::Add for Position { - type Output = Position; - - fn add(self, rhs: Direction) -> Self::Output { - let (row, col) = rhs.as_offset(); - Self { - row: self.row + row, - col: self.col + col, - } - } -} - -fn daily_flip(black_tiles: HashSet) -> HashSet { - let mut new_black_tiles: HashSet = HashSet::new(); - let mut dont_flip_to_black: HashSet = HashSet::new(); - for &tile in &black_tiles { - let mut count_adj_black = 0; - for adj_tile in tile.adjacent() { - if black_tiles.contains(&adj_tile) { - count_adj_black += 1; - } else if !new_black_tiles.contains(&adj_tile) && !dont_flip_to_black.contains(&adj_tile) { - // white adjacent: maybe needs to be flipped to black - let mut white_count_adj_black = 0; - for adj_tile_2 in adj_tile.adjacent() { - if black_tiles.contains(&adj_tile_2) { - white_count_adj_black += 1; - if white_count_adj_black > 2 { break; } - } - } - if white_count_adj_black == 2 { - new_black_tiles.insert(adj_tile); - } else { - // don't check again - dont_flip_to_black.insert(adj_tile); - } - } - } - if count_adj_black == 1 || count_adj_black == 2 { - new_black_tiles.insert(tile); - } - } - new_black_tiles -} - -fn main() { - let directions = INPUT.lines().map(Direction::parse_list).collect::>(); - let flip_tiles = directions.iter().map(|tile| { - tile.iter().fold(Position::default(), |tile, &dir| tile + dir) - }).collect::>(); - let mut black_tiles = HashSet::new(); - for &tile in &flip_tiles { - if black_tiles.contains(&tile) { - black_tiles.remove(&tile); - } else { - black_tiles.insert(tile); - } - } - println!("Black tiles: {}", black_tiles.len()); - for _ in 0..100 { - black_tiles = daily_flip(black_tiles); - } - println!("Black tiles: {}", black_tiles.len()); -} +use std::collections::HashSet; + +const INPUT: &str = include_str!("../../data/day24"); + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +enum Direction { + East, + SouthEast, + SouthWest, + West, + NorthWest, + NorthEast, +} + +impl Direction { + const ALL: [Direction; 6] = [ + Self::East, + Self::SouthEast, + Self::SouthWest, + Self::West, + Self::NorthWest, + Self::NorthEast, + ]; + + fn symbol(self) -> &'static str { + match self { + Self::East => "e", + Self::SouthEast => "se", + Self::SouthWest => "sw", + Self::West => "w", + Self::NorthWest => "nw", + Self::NorthEast => "ne", + } + } + + fn parse_list(line: &str) -> Vec { + let mut line = line.trim(); + let mut result = Vec::new(); + 'outer: while !line.is_empty() { + for &d in Self::ALL.iter() { + if let Some(rem) = line.strip_prefix(d.symbol()) { + result.push(d); + line = rem; + continue 'outer; + } + } + panic!("Invalid directions: {:?}", line); + } + result + } + + // -> (row, col) + fn as_offset(self) -> (i32, i32) { + match self { + Self::East => (0, 1), + Self::SouthEast => (1, 1), + Self::SouthWest => (1, 0), + Self::West => (0, -1), + Self::NorthWest => (-1, -1), + Self::NorthEast => (-1, 0), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Debug)] +struct Position { + row: i32, + // the "0"-column goes from south-west to north-east through (0/0) + col: i32, +} + +impl Position { + fn adjacent(self) -> impl Iterator { + Direction::ALL.iter().map(move |&d| self + d) + } +} + +impl std::ops::Add for Position { + type Output = Position; + + fn add(self, rhs: Direction) -> Self::Output { + let (row, col) = rhs.as_offset(); + Self { + row: self.row + row, + col: self.col + col, + } + } +} + +fn daily_flip(black_tiles: HashSet) -> HashSet { + let mut new_black_tiles: HashSet = HashSet::new(); + let mut dont_flip_to_black: HashSet = HashSet::new(); + for &tile in &black_tiles { + let mut count_adj_black = 0; + for adj_tile in tile.adjacent() { + if black_tiles.contains(&adj_tile) { + count_adj_black += 1; + } else if !new_black_tiles.contains(&adj_tile) && !dont_flip_to_black.contains(&adj_tile) { + // white adjacent: maybe needs to be flipped to black + let mut white_count_adj_black = 0; + for adj_tile_2 in adj_tile.adjacent() { + if black_tiles.contains(&adj_tile_2) { + white_count_adj_black += 1; + if white_count_adj_black > 2 { break; } + } + } + if white_count_adj_black == 2 { + new_black_tiles.insert(adj_tile); + } else { + // don't check again + dont_flip_to_black.insert(adj_tile); + } + } + } + if count_adj_black == 1 || count_adj_black == 2 { + new_black_tiles.insert(tile); + } + } + new_black_tiles +} + +fn main() { + let directions = INPUT.lines().map(Direction::parse_list).collect::>(); + let flip_tiles = directions.iter().map(|tile| { + tile.iter().fold(Position::default(), |tile, &dir| tile + dir) + }).collect::>(); + let mut black_tiles = HashSet::new(); + for &tile in &flip_tiles { + if black_tiles.contains(&tile) { + black_tiles.remove(&tile); + } else { + black_tiles.insert(tile); + } + } + println!("Black tiles: {}", black_tiles.len()); + for _ in 0..100 { + black_tiles = daily_flip(black_tiles); + } + println!("Black tiles: {}", black_tiles.len()); +} diff --git a/src/bin/day25.rs b/src/bin/day25.rs index 19ed1f5..34bd3bf 100644 --- a/src/bin/day25.rs +++ b/src/bin/day25.rs @@ -1,44 +1,44 @@ -const INPUT: &str = include_str!("../../data/day25"); - -const MOD: u32 = 20201227; - -// transform(subject, key) = (subject ** key) mod 20201227 -// -// public_key := transform(7, private_key) -// shared_secret := transform(peer_public_key, local_private_key) - -fn transform(subject: u32, key: u32) -> u32 { - let mut value = 1u64; - let subject = subject as u64; - for _ in 0..key { - value = (value * subject) % (MOD as u64); - } - value as u32 -} - -fn find_key(subject: u32, transformed: u32) -> u32 { - let mut value = 1u64; - let mut key = 0; - let subject = subject as u64; - let transformed = transformed as u64; - while value != transformed { - value = (value * subject) % (MOD as u64); - key += 1; - } - key -} - -fn main() { - let card_pub_key; - let door_pub_key; - { - let mut lines = INPUT.lines(); - card_pub_key = lines.next().unwrap().parse::().unwrap(); - door_pub_key = lines.next().unwrap().parse::().unwrap(); - assert!(lines.next().is_none()); - } - let card_priv_key = find_key(7, card_pub_key); - println!("Card private key: {}", card_priv_key); - let shared_secret = transform(door_pub_key, card_priv_key); - println!("Shared secret: {}", shared_secret); -} +const INPUT: &str = include_str!("../../data/day25"); + +const MOD: u32 = 20201227; + +// transform(subject, key) = (subject ** key) mod 20201227 +// +// public_key := transform(7, private_key) +// shared_secret := transform(peer_public_key, local_private_key) + +fn transform(subject: u32, key: u32) -> u32 { + let mut value = 1u64; + let subject = subject as u64; + for _ in 0..key { + value = (value * subject) % (MOD as u64); + } + value as u32 +} + +fn find_key(subject: u32, transformed: u32) -> u32 { + let mut value = 1u64; + let mut key = 0; + let subject = subject as u64; + let transformed = transformed as u64; + while value != transformed { + value = (value * subject) % (MOD as u64); + key += 1; + } + key +} + +fn main() { + let card_pub_key; + let door_pub_key; + { + let mut lines = INPUT.lines(); + card_pub_key = lines.next().unwrap().parse::().unwrap(); + door_pub_key = lines.next().unwrap().parse::().unwrap(); + assert!(lines.next().is_none()); + } + let card_priv_key = find_key(7, card_pub_key); + println!("Card private key: {}", card_priv_key); + let shared_secret = transform(door_pub_key, card_priv_key); + println!("Shared secret: {}", shared_secret); +}