convert line endings to unix
This commit is contained in:
parent
5215f3bb5f
commit
ca4fb1d3ad
444
src/bin/day17.rs
444
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<usize> {
|
||||
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<i32>;
|
||||
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<bool>,
|
||||
}
|
||||
|
||||
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<usize> {
|
||||
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<bool> {
|
||||
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<usize> {
|
||||
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<i32>;
|
||||
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<bool>,
|
||||
}
|
||||
|
||||
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<usize> {
|
||||
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<bool> {
|
||||
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);
|
||||
}
|
||||
|
338
src/bin/day18.rs
338
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<T>(self, a: T, b: T) -> T
|
||||
where
|
||||
T: std::ops::Add<T, Output=T> + std::ops::Mul<T, Output=T>,
|
||||
{
|
||||
match self {
|
||||
Self::Sum => a + b,
|
||||
Self::Mul => a * b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum Elem<T> {
|
||||
ParenLeft,
|
||||
Op(Op),
|
||||
Item(T),
|
||||
}
|
||||
|
||||
impl<T> Elem<T> {
|
||||
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<T> {
|
||||
elems: Vec<Elem<T>>,
|
||||
same_precedence: bool,
|
||||
}
|
||||
|
||||
impl<T> Stack<T>
|
||||
where
|
||||
T: std::ops::Add<T, Output=T> + std::ops::Mul<T, Output=T> + 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<T> {
|
||||
&self.elems[self.elems.len() - 1]
|
||||
}
|
||||
|
||||
fn _peek_below_top(&self) -> &Elem<T> {
|
||||
&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<T>(mut line: &str, same_precedence: bool) -> T
|
||||
where
|
||||
T: std::ops::Add<T, Output=T> + std::ops::Mul<T, Output=T> + std::str::FromStr + std::fmt::Debug,
|
||||
<T as std::str::FromStr>::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::<T>().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::<u64>(l, true)).sum::<u64>());
|
||||
println!("Sum of evaluated lines (advanced math): {}", INPUT.lines().map(|l| parse::<u64>(l, false)).sum::<u64>());
|
||||
}
|
||||
const INPUT: &str = include_str!("../../data/day18");
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum Op {
|
||||
Sum,
|
||||
Mul,
|
||||
}
|
||||
|
||||
impl Op {
|
||||
fn apply<T>(self, a: T, b: T) -> T
|
||||
where
|
||||
T: std::ops::Add<T, Output=T> + std::ops::Mul<T, Output=T>,
|
||||
{
|
||||
match self {
|
||||
Self::Sum => a + b,
|
||||
Self::Mul => a * b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum Elem<T> {
|
||||
ParenLeft,
|
||||
Op(Op),
|
||||
Item(T),
|
||||
}
|
||||
|
||||
impl<T> Elem<T> {
|
||||
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<T> {
|
||||
elems: Vec<Elem<T>>,
|
||||
same_precedence: bool,
|
||||
}
|
||||
|
||||
impl<T> Stack<T>
|
||||
where
|
||||
T: std::ops::Add<T, Output=T> + std::ops::Mul<T, Output=T> + 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<T> {
|
||||
&self.elems[self.elems.len() - 1]
|
||||
}
|
||||
|
||||
fn _peek_below_top(&self) -> &Elem<T> {
|
||||
&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<T>(mut line: &str, same_precedence: bool) -> T
|
||||
where
|
||||
T: std::ops::Add<T, Output=T> + std::ops::Mul<T, Output=T> + std::str::FromStr + std::fmt::Debug,
|
||||
<T as std::str::FromStr>::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::<T>().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::<u64>(l, true)).sum::<u64>());
|
||||
println!("Sum of evaluated lines (advanced math): {}", INPUT.lines().map(|l| parse::<u64>(l, false)).sum::<u64>());
|
||||
}
|
||||
|
406
src/bin/day19.rs
406
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<u32>),
|
||||
}
|
||||
|
||||
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::<u32>().unwrap()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_line(line: &str) -> (u32, CachedRule) {
|
||||
let colon = line.find(": ").unwrap();
|
||||
let id = line[..colon].parse::<u32>().unwrap();
|
||||
let alts = line[colon+2..].split(" | ").map(Self::parse_part).collect();
|
||||
(id, CachedRule::Alternatives(alts))
|
||||
}
|
||||
}
|
||||
|
||||
fn _make_combs(target: &mut HashSet<String>, current: &mut String, list: &[Arc<HashSet<String>>]) {
|
||||
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<Rule>),
|
||||
CachedStrings(Arc<HashSet<String>>, HashSet<usize>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Grammar {
|
||||
expansions: HashMap<u32, CachedRule>,
|
||||
}
|
||||
|
||||
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<u32>, ndx: u32) -> Option<Arc<HashSet<String>>> {
|
||||
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::<String>::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<usize> = 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<u32> = 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::<Vec<String>>();
|
||||
}
|
||||
{
|
||||
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<u32>),
|
||||
}
|
||||
|
||||
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::<u32>().unwrap()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_line(line: &str) -> (u32, CachedRule) {
|
||||
let colon = line.find(": ").unwrap();
|
||||
let id = line[..colon].parse::<u32>().unwrap();
|
||||
let alts = line[colon+2..].split(" | ").map(Self::parse_part).collect();
|
||||
(id, CachedRule::Alternatives(alts))
|
||||
}
|
||||
}
|
||||
|
||||
fn _make_combs(target: &mut HashSet<String>, current: &mut String, list: &[Arc<HashSet<String>>]) {
|
||||
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<Rule>),
|
||||
CachedStrings(Arc<HashSet<String>>, HashSet<usize>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Grammar {
|
||||
expansions: HashMap<u32, CachedRule>,
|
||||
}
|
||||
|
||||
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<u32>, ndx: u32) -> Option<Arc<HashSet<String>>> {
|
||||
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::<String>::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<usize> = 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<u32> = 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::<Vec<String>>();
|
||||
}
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
932
src/bin/day20.rs
932
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<I>(bits: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item=bool>,
|
||||
{
|
||||
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<Mask> {
|
||||
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<bool>,
|
||||
}
|
||||
|
||||
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::<u32>().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::<Vec<_>>();
|
||||
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<Edge, Side>,
|
||||
side_to_edge: HashMap<Side, Edge>,
|
||||
}
|
||||
|
||||
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<u32, TileDetailed> = INPUT.trim().split("\n\n").map(TileDetailed::parse).collect();
|
||||
let mut edges: HashMap<Edge, Vec<(u32, Side)>> = 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::<Vec<_>>();
|
||||
let mut single_edges_per_tile = HashMap::<u32, Vec<Side>>::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::<Vec<_>>();
|
||||
let corners_prod = corners.iter().map(|&tile_id| tile_id as u64).product::<u64>();
|
||||
println!("Product of corner tile ids: {}", corners_prod);
|
||||
|
||||
let mut grid_tiles: [[Option<TilePlacement>; 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<I>(bits: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item=bool>,
|
||||
{
|
||||
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<Mask> {
|
||||
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<bool>,
|
||||
}
|
||||
|
||||
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::<u32>().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::<Vec<_>>();
|
||||
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<Edge, Side>,
|
||||
side_to_edge: HashMap<Side, Edge>,
|
||||
}
|
||||
|
||||
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<u32, TileDetailed> = INPUT.trim().split("\n\n").map(TileDetailed::parse).collect();
|
||||
let mut edges: HashMap<Edge, Vec<(u32, Side)>> = 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::<Vec<_>>();
|
||||
let mut single_edges_per_tile = HashMap::<u32, Vec<Side>>::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::<Vec<_>>();
|
||||
let corners_prod = corners.iter().map(|&tile_id| tile_id as u64).product::<u64>();
|
||||
println!("Product of corner tile ids: {}", corners_prod);
|
||||
|
||||
let mut grid_tiles: [[Option<TilePlacement>; 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);
|
||||
}
|
||||
|
114
src/bin/day21.rs
114
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::<Vec<_>>();
|
||||
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::<HashSet<_>>()
|
||||
});
|
||||
(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::<usize>(),
|
||||
);
|
||||
println!("Dangerous ingredients: {}", allergen_map.values().cloned().collect::<Vec<_>>().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::<Vec<_>>();
|
||||
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::<HashSet<_>>()
|
||||
});
|
||||
(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::<usize>(),
|
||||
);
|
||||
println!("Dangerous ingredients: {}", allergen_map.values().cloned().collect::<Vec<_>>().join(","));
|
||||
}
|
||||
|
196
src/bin/day22.rs
196
src/bin/day22.rs
@ -1,98 +1,98 @@
|
||||
use std::collections::{VecDeque, HashSet};
|
||||
|
||||
type Deck = VecDeque<u32>;
|
||||
|
||||
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::<u32>().unwrap();
|
||||
let block = block[1..].strip_prefix(":").unwrap().trim();
|
||||
let deck = block.lines().map(|s| s.parse::<u32>().unwrap()).collect();
|
||||
(player, deck)
|
||||
}
|
||||
|
||||
fn score(deck: &Deck) -> u64 {
|
||||
deck.iter().rev().enumerate().map(|(pos, &value)| {
|
||||
((pos + 1) as u64) * (value as u64)
|
||||
}).sum::<u64>()
|
||||
}
|
||||
|
||||
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<u32>;
|
||||
|
||||
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::<u32>().unwrap();
|
||||
let block = block[1..].strip_prefix(":").unwrap().trim();
|
||||
let deck = block.lines().map(|s| s.parse::<u32>().unwrap()).collect();
|
||||
(player, deck)
|
||||
}
|
||||
|
||||
fn score(deck: &Deck) -> u64 {
|
||||
deck.iter().rev().enumerate().map(|(pos, &value)| {
|
||||
((pos + 1) as u64) * (value as u64)
|
||||
}).sum::<u64>()
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
434
src/bin/day23.rs
434
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<Item> {
|
||||
if self.0 == MARK_MISSING {
|
||||
None
|
||||
} else {
|
||||
Some(self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Item> for OptionItem {
|
||||
fn from(i: Item) -> Self {
|
||||
assert_ne!(i, MARK_MISSING);
|
||||
Self(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<Item>> for OptionItem {
|
||||
fn from(i: Option<Item>) -> 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<OptionItem>,
|
||||
}
|
||||
|
||||
impl Default for Cups {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
current: OptionItem::NONE,
|
||||
last: OptionItem::NONE,
|
||||
next_cup: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Cups {
|
||||
fn last(&self) -> Option<Item> {
|
||||
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<I>(&mut self, mut after: Item, iter: I)
|
||||
where
|
||||
I: IntoIterator<Item=Item>,
|
||||
{
|
||||
for item in iter {
|
||||
self.insert_after(after, item);
|
||||
after = item;
|
||||
}
|
||||
}
|
||||
|
||||
fn extend<I>(&mut self, iter: I)
|
||||
where
|
||||
I: IntoIterator<Item=Item>,
|
||||
{
|
||||
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<Item=Item> + '_ {
|
||||
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<Item=Item> + '_ {
|
||||
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<Item=Item> + '_ {
|
||||
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<usize>) -> impl Iterator<Item=Item> + '_ {
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>().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::<u64>());
|
||||
}
|
||||
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<Item> {
|
||||
if self.0 == MARK_MISSING {
|
||||
None
|
||||
} else {
|
||||
Some(self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Item> for OptionItem {
|
||||
fn from(i: Item) -> Self {
|
||||
assert_ne!(i, MARK_MISSING);
|
||||
Self(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<Item>> for OptionItem {
|
||||
fn from(i: Option<Item>) -> 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<OptionItem>,
|
||||
}
|
||||
|
||||
impl Default for Cups {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
current: OptionItem::NONE,
|
||||
last: OptionItem::NONE,
|
||||
next_cup: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Cups {
|
||||
fn last(&self) -> Option<Item> {
|
||||
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<I>(&mut self, mut after: Item, iter: I)
|
||||
where
|
||||
I: IntoIterator<Item=Item>,
|
||||
{
|
||||
for item in iter {
|
||||
self.insert_after(after, item);
|
||||
after = item;
|
||||
}
|
||||
}
|
||||
|
||||
fn extend<I>(&mut self, iter: I)
|
||||
where
|
||||
I: IntoIterator<Item=Item>,
|
||||
{
|
||||
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<Item=Item> + '_ {
|
||||
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<Item=Item> + '_ {
|
||||
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<Item=Item> + '_ {
|
||||
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<usize>) -> impl Iterator<Item=Item> + '_ {
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>().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::<u64>());
|
||||
}
|
||||
|
280
src/bin/day24.rs
280
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<Self> {
|
||||
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<Item=Self> {
|
||||
Direction::ALL.iter().map(move |&d| self + d)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Direction> 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<Position>) -> HashSet<Position> {
|
||||
let mut new_black_tiles: HashSet<Position> = HashSet::new();
|
||||
let mut dont_flip_to_black: HashSet<Position> = 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::<Vec<_>>();
|
||||
let flip_tiles = directions.iter().map(|tile| {
|
||||
tile.iter().fold(Position::default(), |tile, &dir| tile + dir)
|
||||
}).collect::<Vec<_>>();
|
||||
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<Self> {
|
||||
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<Item=Self> {
|
||||
Direction::ALL.iter().map(move |&d| self + d)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Direction> 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<Position>) -> HashSet<Position> {
|
||||
let mut new_black_tiles: HashSet<Position> = HashSet::new();
|
||||
let mut dont_flip_to_black: HashSet<Position> = 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::<Vec<_>>();
|
||||
let flip_tiles = directions.iter().map(|tile| {
|
||||
tile.iter().fold(Position::default(), |tile, &dir| tile + dir)
|
||||
}).collect::<Vec<_>>();
|
||||
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());
|
||||
}
|
||||
|
@ -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::<u32>().unwrap();
|
||||
door_pub_key = lines.next().unwrap().parse::<u32>().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::<u32>().unwrap();
|
||||
door_pub_key = lines.next().unwrap().parse::<u32>().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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user