generated from janezicmatej/aoc-template
Compare commits
6 Commits
0000033086
...
day20
| Author | SHA1 | Date | |
|---|---|---|---|
|
000003902f
|
|||
|
00000380c1
|
|||
|
0000037022
|
|||
|
00000360db
|
|||
|
00000350d1
|
|||
|
0000034077
|
14
data/examples/18.txt
Normal file
14
data/examples/18.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
R 6 (#70c710)
|
||||||
|
D 5 (#0dc571)
|
||||||
|
L 2 (#5713f0)
|
||||||
|
D 2 (#d2c081)
|
||||||
|
R 2 (#59c680)
|
||||||
|
D 2 (#411b91)
|
||||||
|
L 5 (#8ceee2)
|
||||||
|
U 2 (#caa173)
|
||||||
|
L 1 (#1b58a2)
|
||||||
|
U 2 (#caa171)
|
||||||
|
R 2 (#7807d2)
|
||||||
|
U 3 (#a77fa3)
|
||||||
|
L 2 (#015232)
|
||||||
|
U 2 (#7a21e3)
|
||||||
17
data/examples/19.txt
Normal file
17
data/examples/19.txt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
px{a<2006:qkq,m>2090:A,rfg}
|
||||||
|
pv{a>1716:R,A}
|
||||||
|
lnx{m>1548:A,A}
|
||||||
|
rfg{s<537:gd,x>2440:R,A}
|
||||||
|
qs{s>3448:A,lnx}
|
||||||
|
qkq{x<1416:A,crn}
|
||||||
|
crn{x>2662:A,R}
|
||||||
|
in{s<1351:px,qqz}
|
||||||
|
qqz{s>2770:qs,m<1801:hdj,R}
|
||||||
|
gd{a>3333:R,R}
|
||||||
|
hdj{m>838:A,pv}
|
||||||
|
|
||||||
|
{x=787,m=2655,a=1222,s=2876}
|
||||||
|
{x=1679,m=44,a=2067,s=496}
|
||||||
|
{x=2036,m=264,a=79,s=2244}
|
||||||
|
{x=2461,m=1339,a=466,s=291}
|
||||||
|
{x=2127,m=1623,a=2188,s=1013}
|
||||||
5
data/examples/20.txt
Normal file
5
data/examples/20.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
broadcaster -> a, b, c
|
||||||
|
%a -> b
|
||||||
|
%b -> c
|
||||||
|
%c -> inv
|
||||||
|
&inv -> a
|
||||||
126
src/bin/18.rs
Normal file
126
src/bin/18.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Direction {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ParseDirectionError;
|
||||||
|
|
||||||
|
impl FromStr for Direction {
|
||||||
|
type Err = ParseDirectionError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
use Direction::*;
|
||||||
|
|
||||||
|
Ok(match s {
|
||||||
|
"U" | "3" => Up,
|
||||||
|
"D" | "1" => Down,
|
||||||
|
"L" | "2" => Left,
|
||||||
|
"R" | "0" => Right,
|
||||||
|
_ => return Err(ParseDirectionError),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Direction> for (isize, isize) {
|
||||||
|
fn from(value: Direction) -> Self {
|
||||||
|
use Direction::*;
|
||||||
|
match value {
|
||||||
|
Up => (-1, 0),
|
||||||
|
Down => (1, 0),
|
||||||
|
Left => (0, -1),
|
||||||
|
Right => (0, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_area(border: &[(isize, isize)], border_length: isize) -> isize {
|
||||||
|
// get area with shoelace formula (trapezoid variant)
|
||||||
|
// https://en.wikipedia.org/wiki/Shoelace_formula
|
||||||
|
let mut shoelace: isize = 0;
|
||||||
|
for n in 0..border.len() {
|
||||||
|
let (y1, x1) = border[n];
|
||||||
|
let (y2, x2) = border[(n + 1) % border.len()];
|
||||||
|
shoelace += (y1 + y2) * (x1 - x2);
|
||||||
|
}
|
||||||
|
let area = shoelace / 2;
|
||||||
|
|
||||||
|
// get interior by inverting pick's theorem formula
|
||||||
|
// https://en.wikipedia.org/wiki/Pick%27s_theorem
|
||||||
|
let interior = area + 1 - border_length / 2;
|
||||||
|
interior + border_length
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_border(instructions: &[(Direction, isize)]) -> (Vec<(isize, isize)>, isize) {
|
||||||
|
let mut border = Vec::new();
|
||||||
|
let mut border_length = 0;
|
||||||
|
let (mut sy, mut sx) = (0, 0);
|
||||||
|
border.push((sy, sx));
|
||||||
|
|
||||||
|
for (d, l) in instructions.iter().copied() {
|
||||||
|
let (dy, dx) = d.into();
|
||||||
|
(sy, sx) = (sy + l * dy, sx + l * dx);
|
||||||
|
border.push((sy, sx));
|
||||||
|
border_length += l;
|
||||||
|
}
|
||||||
|
|
||||||
|
border.pop();
|
||||||
|
|
||||||
|
(border, border_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_one(input: &str) -> Option<isize> {
|
||||||
|
let instructions: Vec<(Direction, isize)> = input
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
let [d, l, _] = line.splitn(3, ' ').collect::<Vec<_>>().try_into().unwrap();
|
||||||
|
(d.parse().unwrap(), l.parse::<isize>().unwrap())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let (border, border_length) = get_border(&instructions);
|
||||||
|
Some(get_area(&border, border_length))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join_option_tuple<T, U>((a, b): (Option<T>, Option<U>)) -> Option<(T, U)> {
|
||||||
|
Some((a?, b?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_two(input: &str) -> Option<isize> {
|
||||||
|
let instructions: Vec<(Direction, isize)> = input
|
||||||
|
.lines()
|
||||||
|
.filter_map(|line| line.split_once(" (#"))
|
||||||
|
.filter_map(|(_, h)| h.strip_suffix(')'))
|
||||||
|
.map(|h| h.split_at(h.len() - 1))
|
||||||
|
.map(|(hex, dir)| (dir.parse().ok(), isize::from_str_radix(hex, 16).ok()))
|
||||||
|
.filter_map(join_option_tuple)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let (border, border_length) = get_border(&instructions);
|
||||||
|
Some(get_area(&border, border_length))
|
||||||
|
}
|
||||||
|
|
||||||
|
aoc::solution!(18);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_part_one() {
|
||||||
|
assert_eq!(
|
||||||
|
part_one(&aoc::template::read_file("examples", 18)),
|
||||||
|
Some(62)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_part_two() {
|
||||||
|
assert_eq!(
|
||||||
|
part_two(&aoc::template::read_file("examples", 18)),
|
||||||
|
Some(952408144115)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
387
src/bin/19.rs
Normal file
387
src/bin/19.rs
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
ops::{Index, IndexMut},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||||
|
enum Gear {
|
||||||
|
X,
|
||||||
|
M,
|
||||||
|
A,
|
||||||
|
S,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct GearParseError;
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Gear {
|
||||||
|
type Error = GearParseError;
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
use Gear::*;
|
||||||
|
Ok(match value {
|
||||||
|
"x" => X,
|
||||||
|
"m" => M,
|
||||||
|
"a" => A,
|
||||||
|
"s" => S,
|
||||||
|
_ => return Err(GearParseError),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||||
|
enum Comparator {
|
||||||
|
Lt,
|
||||||
|
Gt,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ComparatorParseError;
|
||||||
|
|
||||||
|
impl TryFrom<char> for Comparator {
|
||||||
|
type Error = ComparatorParseError;
|
||||||
|
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||||
|
use Comparator::*;
|
||||||
|
Ok(match value {
|
||||||
|
'<' => Lt,
|
||||||
|
'>' => Gt,
|
||||||
|
_ => return Err(ComparatorParseError),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||||
|
enum Resolver {
|
||||||
|
Accepted,
|
||||||
|
Rejected,
|
||||||
|
Delegated(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Resolver {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
use Resolver::*;
|
||||||
|
match value {
|
||||||
|
"A" => Accepted,
|
||||||
|
"R" => Rejected,
|
||||||
|
x => Delegated(x.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||||
|
struct Workflow {
|
||||||
|
workflows: Vec<WorkflowInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ParseWorkflowError;
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Workflow {
|
||||||
|
type Error = ParseWorkflowError;
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
workflows: value.split(',').filter_map(|x| x.try_into().ok()).collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||||
|
enum WorkflowInner {
|
||||||
|
Resolver(Resolver),
|
||||||
|
Rule((Gear, Comparator, usize, Resolver)),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ParseWorkflowInnerError;
|
||||||
|
|
||||||
|
impl TryFrom<&str> for WorkflowInner {
|
||||||
|
type Error = ParseWorkflowInnerError;
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
if !value.contains(':') {
|
||||||
|
return Ok(WorkflowInner::Resolver(value.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (rest, resolver) = value.split_once(':').unwrap();
|
||||||
|
let resolver = resolver.into();
|
||||||
|
|
||||||
|
let (gear, number) = rest.split_once(|x| x == '<' || x == '>').unwrap();
|
||||||
|
let gear = gear.try_into().map_err(|_| ParseWorkflowInnerError)?;
|
||||||
|
let number = number.parse().map_err(|_| ParseWorkflowInnerError)?;
|
||||||
|
|
||||||
|
let comparator = if value.contains('<') { '<' } else { '>' };
|
||||||
|
let comparator = comparator.try_into().map_err(|_| ParseWorkflowInnerError)?;
|
||||||
|
|
||||||
|
Ok(WorkflowInner::Rule((gear, comparator, number, resolver)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Xmas {
|
||||||
|
x: usize,
|
||||||
|
m: usize,
|
||||||
|
a: usize,
|
||||||
|
s: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ParseXmasError;
|
||||||
|
|
||||||
|
impl FromStr for Xmas {
|
||||||
|
type Err = ParseXmasError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let s = s.strip_prefix('{').ok_or(ParseXmasError)?;
|
||||||
|
let s = s.strip_suffix('}').ok_or(ParseXmasError)?;
|
||||||
|
let xmas: Vec<_> = s
|
||||||
|
.split(',')
|
||||||
|
.filter_map(|x| x.split_once('='))
|
||||||
|
.map(|x| x.1)
|
||||||
|
.filter_map(|x| x.parse().ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
x: xmas[0],
|
||||||
|
m: xmas[1],
|
||||||
|
a: xmas[2],
|
||||||
|
s: xmas[3],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<Gear> for Xmas {
|
||||||
|
type Output = usize;
|
||||||
|
fn index(&self, index: Gear) -> &Self::Output {
|
||||||
|
use Gear::*;
|
||||||
|
match index {
|
||||||
|
X => &self.x,
|
||||||
|
M => &self.m,
|
||||||
|
A => &self.a,
|
||||||
|
S => &self.s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<Gear> for Xmas {
|
||||||
|
fn index_mut(&mut self, index: Gear) -> &mut Self::Output {
|
||||||
|
use Gear::*;
|
||||||
|
match index {
|
||||||
|
X => &mut self.x,
|
||||||
|
M => &mut self.m,
|
||||||
|
A => &mut self.a,
|
||||||
|
S => &mut self.s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Xmas {
|
||||||
|
fn sum(&self) -> usize {
|
||||||
|
self.x + self.m + self.a + self.s
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply(&self, workflow: &Workflow) -> Resolver {
|
||||||
|
for w in workflow.workflows.iter() {
|
||||||
|
match w {
|
||||||
|
WorkflowInner::Resolver(x) => {
|
||||||
|
return x.clone();
|
||||||
|
}
|
||||||
|
WorkflowInner::Rule((g, c, n, r)) => {
|
||||||
|
let is_match = match c {
|
||||||
|
Comparator::Gt => self[*g] > *n,
|
||||||
|
Comparator::Lt => self[*g] < *n,
|
||||||
|
};
|
||||||
|
if is_match {
|
||||||
|
return r.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct XmasRange {
|
||||||
|
x: (usize, usize),
|
||||||
|
m: (usize, usize),
|
||||||
|
a: (usize, usize),
|
||||||
|
s: (usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<Gear> for XmasRange {
|
||||||
|
type Output = (usize, usize);
|
||||||
|
fn index(&self, index: Gear) -> &Self::Output {
|
||||||
|
use Gear::*;
|
||||||
|
match index {
|
||||||
|
X => &self.x,
|
||||||
|
M => &self.m,
|
||||||
|
A => &self.a,
|
||||||
|
S => &self.s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<Gear> for XmasRange {
|
||||||
|
fn index_mut(&mut self, index: Gear) -> &mut Self::Output {
|
||||||
|
use Gear::*;
|
||||||
|
match index {
|
||||||
|
X => &mut self.x,
|
||||||
|
M => &mut self.m,
|
||||||
|
A => &mut self.a,
|
||||||
|
S => &mut self.s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XmasRange {
|
||||||
|
fn size(&self) -> usize {
|
||||||
|
(self.x.1 - self.x.0 + 1)
|
||||||
|
* (self.m.1 - self.m.0 + 1)
|
||||||
|
* (self.a.1 - self.a.0 + 1)
|
||||||
|
* (self.s.1 - self.s.0 + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn divide(self, workflow: &Workflow) -> Vec<(Self, Resolver)> {
|
||||||
|
let mut processed = Vec::new();
|
||||||
|
let mut rest = vec![self];
|
||||||
|
|
||||||
|
for w in workflow.workflows.iter() {
|
||||||
|
let mut new_rest = Vec::new();
|
||||||
|
|
||||||
|
while let Some(xmas_range) = rest.pop() {
|
||||||
|
match w {
|
||||||
|
WorkflowInner::Resolver(r) => processed.push((xmas_range, r.clone())),
|
||||||
|
WorkflowInner::Rule((g, c, n, r)) => {
|
||||||
|
let compare = |x: usize, y: usize| match c {
|
||||||
|
Comparator::Gt => x > y,
|
||||||
|
Comparator::Lt => x < y,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut min_ok = usize::MAX;
|
||||||
|
let mut max_ok = usize::MIN;
|
||||||
|
let mut min_e = usize::MAX;
|
||||||
|
let mut max_e = usize::MIN;
|
||||||
|
|
||||||
|
for i in xmas_range[*g].0..=xmas_range[*g].1 {
|
||||||
|
if compare(i, *n) {
|
||||||
|
max_ok = if max_ok < i { i } else { max_ok };
|
||||||
|
min_ok = if min_ok > i { i } else { min_ok };
|
||||||
|
} else {
|
||||||
|
max_e = if max_e < i { i } else { max_e };
|
||||||
|
min_e = if min_e > i { i } else { min_e };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if min_e <= max_e {
|
||||||
|
let mut r = xmas_range;
|
||||||
|
r[*g] = (min_e, max_e);
|
||||||
|
new_rest.push(r);
|
||||||
|
}
|
||||||
|
if min_ok <= max_ok {
|
||||||
|
let mut p = xmas_range;
|
||||||
|
p[*g] = (min_ok, max_ok);
|
||||||
|
processed.push((p, r.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rest = new_rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
processed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_map(input: &str) -> HashMap<&str, Workflow> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
for (s, w) in input
|
||||||
|
.split("\n\n")
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.lines()
|
||||||
|
.filter_map(|x| x.strip_suffix('}'))
|
||||||
|
.filter_map(|x| x.split_once('{'))
|
||||||
|
.filter_map(|(s, w)| w.try_into().ok().map(|x| (s, x)))
|
||||||
|
{
|
||||||
|
map.insert(s, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_one(input: &str) -> Option<usize> {
|
||||||
|
let map = build_map(input);
|
||||||
|
let xmas_vec: Vec<Xmas> = input
|
||||||
|
.split("\n\n")
|
||||||
|
.nth(1)?
|
||||||
|
.lines()
|
||||||
|
.filter_map(|x| x.parse().ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut total = 0;
|
||||||
|
|
||||||
|
'outer: for xmas in xmas_vec.into_iter() {
|
||||||
|
let mut workflow = &map["in"];
|
||||||
|
'apply: loop {
|
||||||
|
match xmas.apply(workflow) {
|
||||||
|
Resolver::Accepted => break 'apply,
|
||||||
|
Resolver::Rejected => continue 'outer,
|
||||||
|
Resolver::Delegated(x) => workflow = &map[x.as_str()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total += xmas.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_two(input: &str) -> Option<usize> {
|
||||||
|
let map = build_map(input);
|
||||||
|
|
||||||
|
let mut finished = Vec::new();
|
||||||
|
let mut stack = vec![(
|
||||||
|
XmasRange {
|
||||||
|
x: (1, 4000),
|
||||||
|
m: (1, 4000),
|
||||||
|
a: (1, 4000),
|
||||||
|
s: (1, 4000),
|
||||||
|
},
|
||||||
|
Resolver::Delegated("in".to_string()),
|
||||||
|
)];
|
||||||
|
|
||||||
|
while let Some((xmas_range, resolver)) = stack.pop() {
|
||||||
|
match resolver {
|
||||||
|
Resolver::Accepted => finished.push(xmas_range),
|
||||||
|
Resolver::Rejected => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Resolver::Delegated(x) => {
|
||||||
|
let workflow = &map[x.as_str()];
|
||||||
|
stack.append(&mut xmas_range.divide(workflow))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(finished.iter().map(XmasRange::size).sum())
|
||||||
|
}
|
||||||
|
aoc::solution!(19);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_part_one() {
|
||||||
|
assert_eq!(
|
||||||
|
part_one(&aoc::template::read_file("examples", 19)),
|
||||||
|
Some(19114)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_part_two() {
|
||||||
|
assert_eq!(
|
||||||
|
part_two(&aoc::template::read_file("examples", 19)),
|
||||||
|
Some(167409079868000)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
187
src/bin/20.rs
Normal file
187
src/bin/20.rs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
use std::{
|
||||||
|
collections::{HashMap, VecDeque},
|
||||||
|
mem::swap,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
enum Module {
|
||||||
|
Broadcaster,
|
||||||
|
FlipFlop(bool),
|
||||||
|
Conjuction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ParseModuleError;
|
||||||
|
|
||||||
|
impl FromStr for Module {
|
||||||
|
type Err = ParseModuleError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"%" => Module::FlipFlop(false),
|
||||||
|
"&" => Module::Conjuction,
|
||||||
|
_ => Module::Broadcaster,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Node {
|
||||||
|
index: usize,
|
||||||
|
module: Module,
|
||||||
|
inputs: Vec<usize>,
|
||||||
|
outputs: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_input(input: &str) -> Vec<Node> {
|
||||||
|
let mut nodes = Vec::new();
|
||||||
|
let mut mapper: HashMap<String, usize> = HashMap::new();
|
||||||
|
|
||||||
|
for line in input.lines() {
|
||||||
|
let (from, _) = line.split_once(" -> ").unwrap();
|
||||||
|
let (module, mut name) = from.split_at(1);
|
||||||
|
let module: Module = module.parse().unwrap();
|
||||||
|
if module == Module::Broadcaster {
|
||||||
|
name = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
*mapper.entry(name.to_string()).or_default() = nodes.len();
|
||||||
|
|
||||||
|
let l = Node {
|
||||||
|
module,
|
||||||
|
index: nodes.len(),
|
||||||
|
inputs: Vec::new(),
|
||||||
|
outputs: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.push(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in input.lines() {
|
||||||
|
let (from, to) = line.split_once(" -> ").unwrap();
|
||||||
|
let (module, mut name) = from.split_at(1);
|
||||||
|
let module: Module = module.parse().unwrap();
|
||||||
|
if module == Module::Broadcaster {
|
||||||
|
name = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = mapper[name];
|
||||||
|
for destination in to.split(", ") {
|
||||||
|
let to_index = mapper[destination];
|
||||||
|
nodes[index].outputs.push(to_index);
|
||||||
|
nodes[to_index].inputs.push(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_one(input: &str) -> Option<usize> {
|
||||||
|
let mut nodes = parse_input(input);
|
||||||
|
|
||||||
|
let mut graph = vec![vec![None; nodes.len()]; nodes.len()];
|
||||||
|
|
||||||
|
let mut highs = 0;
|
||||||
|
let mut lows = 0;
|
||||||
|
|
||||||
|
let broadcaster = nodes
|
||||||
|
.iter()
|
||||||
|
.find(|x| x.module == Module::Broadcaster)?
|
||||||
|
.index;
|
||||||
|
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let mut stack = VecDeque::from([broadcaster]);
|
||||||
|
while !stack.is_empty() {
|
||||||
|
let mut new_stack = VecDeque::new();
|
||||||
|
let mut new_graph = graph.clone();
|
||||||
|
|
||||||
|
while let Some(index) = stack.pop_front() {
|
||||||
|
let node = &nodes[index];
|
||||||
|
let mut new_module = None;
|
||||||
|
|
||||||
|
match node.module {
|
||||||
|
Module::Broadcaster => {
|
||||||
|
for dest_index in node.outputs.iter() {
|
||||||
|
new_graph[index][*dest_index] = Some(false);
|
||||||
|
new_stack.push_back(*dest_index);
|
||||||
|
lows += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Module::FlipFlop(high) => {
|
||||||
|
let mut swapper = None;
|
||||||
|
for in_index in nodes[index].inputs.iter() {
|
||||||
|
let value = &mut graph[*in_index][index];
|
||||||
|
if value.is_some() {
|
||||||
|
debug_assert!(swapper.is_none());
|
||||||
|
swap(&mut swapper, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !swapper.unwrap() {
|
||||||
|
let signal = !high;
|
||||||
|
|
||||||
|
for dest_index in nodes[index].outputs.iter() {
|
||||||
|
new_graph[index][*dest_index] = Some(signal);
|
||||||
|
new_stack.push_back(*dest_index);
|
||||||
|
if signal {
|
||||||
|
highs += 1;
|
||||||
|
} else {
|
||||||
|
lows += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_module = Some(Module::FlipFlop(signal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Module::Conjuction => {
|
||||||
|
let mut all = true;
|
||||||
|
|
||||||
|
for in_index in nodes[index].inputs.iter() {
|
||||||
|
let value = graph[*in_index][index];
|
||||||
|
all &= value.unwrap_or(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for dest_index in nodes[index].outputs.iter() {
|
||||||
|
new_graph[index][*dest_index] = Some(!all);
|
||||||
|
new_stack.push_back(*dest_index);
|
||||||
|
if !all {
|
||||||
|
highs += 1;
|
||||||
|
} else {
|
||||||
|
lows += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = &mut nodes[index];
|
||||||
|
if let Some(module) = new_module {
|
||||||
|
node.module = module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stack = new_stack;
|
||||||
|
graph = new_graph;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(highs * lows)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part_two(input: &str) -> Option<u32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
aoc::solution!(20);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_part_one() {
|
||||||
|
assert_eq!(part_one(&aoc::template::read_file("examples", 20)), None);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_part_two() {
|
||||||
|
assert_eq!(part_two(&aoc::template::read_file("examples", 20)), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user