Day 20 - Part 1

Signed-off-by: Anthony Oteri <anthony.oteri@gmail.com>
This commit is contained in:
Anthony Oteri
2023-12-20 17:08:12 -05:00
parent 1d110c389e
commit ee2bc2bc73
11 changed files with 415 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
[package]
name = "day-20"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
itertools = { workspace = true }
nom = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
miette = { workspace = true }
thiserror = { workspace = true }
dhat = { workspace = true }
[dev-dependencies]
divan = { workspace = true }
env_logger = { workspace = true }
test-log = { workspace = true }
rstest = { workspace = true }
[[bench]]
name = "day-20"
path = "benches/benchmark.rs"
harness = false
[features]
dhat-heap = []
+15
View File
@@ -0,0 +1,15 @@
use day_20::*;
fn main() {
divan::main();
}
#[divan::bench]
fn part1() {
part1::process(divan::black_box(include_str!("../input.txt"))).unwrap();
}
#[divan::bench]
fn part2() {
part2::process(divan::black_box(include_str!("../input.txt"))).unwrap();
}
+58
View File
@@ -0,0 +1,58 @@
%ls -> cs, jd
%st -> jh, bj
%hc -> tn
%xs -> rr
%pq -> tl, cs
%rj -> km, ck
%lb -> cg
%zz -> dx, ch
%kk -> cs, tc
%hr -> ck, pl
%pj -> cs, pr
%pr -> zl, cs
%tl -> vn, cs
%tc -> cs, hn
&cs -> hn, pj, qb, zl
%lv -> hr
%vn -> cs, ls
&mp -> dr
broadcaster -> ns, pj, xz, sg
%gg -> jh, nx
%cr -> jg, jh
%ch -> dx
&dx -> zj, xz, mp, zn, xs, hc
%fl -> jh, ps
%hn -> pq
%qp -> dx, zj
%bp -> zr
%kl -> lb
%pl -> ck, xq
%rm -> zz, dx
%rz -> jh, fl
%pm -> cs, kk
%zl -> pm
%rk -> ck, kl
&dr -> rx
%cg -> lv, ck
&qt -> dr
%jg -> jh
&qb -> dr
&ck -> lb, lv, ns, kl, qt
%zr -> st
%dd -> dx, qp
%kd -> rk, ck
%xq -> rj, ck
%sg -> rz, jh
%zj -> hc
%tn -> dx, xs
%jd -> cs
%rr -> rm, dx
&jh -> ng, bp, zr, sg, bj
%km -> ck
%ps -> bp, jh
%zn -> dd
%bj -> gg
%nx -> jh, cr
%xz -> dx, zn
&ng -> dr
%ns -> ck, kd
+21
View File
@@ -0,0 +1,21 @@
use day_20::part1::process;
use miette::Context;
#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
#[tracing::instrument]
fn main() -> miette::Result<()> {
#[cfg(feature = "dhat-heap")]
let _profiler = dhat::Profiler::new_heap();
#[cfg(not(feature = "dhat-heap"))]
tracing_subscriber::fmt::init();
let file = include_str!("../../input.txt");
let result = process(file).context("process part 1")?;
println!("{}", result);
Ok(())
}
+21
View File
@@ -0,0 +1,21 @@
use day_20::part2::process;
use miette::Context;
#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
#[tracing::instrument]
fn main() -> miette::Result<()> {
#[cfg(feature = "dhat-heap")]
let _profiler = dhat::Profiler::new_heap();
#[cfg(not(feature = "dhat-heap"))]
tracing_subscriber::fmt::init();
let file = include_str!("../../input.txt");
let result = process(file).context("process part 1")?;
println!("{}", result);
Ok(())
}
+9
View File
@@ -0,0 +1,9 @@
use miette::Diagnostic;
use thiserror::Error;
#[derive(Error, Diagnostic, Debug)]
pub enum AocError {
#[error(transparent)]
#[diagnostic(code(aoc::io_error))]
IoError(#[from] std::io::Error),
}
+4
View File
@@ -0,0 +1,4 @@
pub mod error;
pub mod part1;
pub mod part2;
+230
View File
@@ -0,0 +1,230 @@
use std::collections::{HashMap, HashSet, VecDeque};
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{alpha1, line_ending},
multi::separated_list1,
sequence::preceded,
IResult,
};
use crate::error::AocError;
#[derive(Debug)]
enum State {
On,
Off,
}
#[derive(Debug, Copy, Clone)]
enum Signal {
High,
Low,
}
#[derive(Debug)]
enum ModuleType<'a> {
Broadcaster,
FlipFlop { state: State },
Conjunction { inputs: HashMap<&'a str, Signal> },
Test,
}
#[derive(Debug)]
struct Module<'a> {
id: &'a str,
module_type: ModuleType<'a>,
outputs: Vec<&'a str>,
}
impl<'a> Module<'a> {
#[tracing::instrument]
fn process(&mut self, from: &'a str, signal: &Signal) -> Vec<(&'a str, &'a str, Signal)> {
match (&mut self.module_type, signal) {
(ModuleType::Broadcaster, signal) => self
.outputs
.iter()
.map(|o| (self.id, *o, *signal))
.collect(),
(ModuleType::FlipFlop { .. }, Signal::High) => {
vec![]
}
(ModuleType::FlipFlop { state: State::On }, Signal::Low) => {
self.module_type = ModuleType::FlipFlop { state: State::Off };
self.outputs
.iter()
.map(|o| (self.id, *o, Signal::Low))
.collect()
}
(ModuleType::FlipFlop { state: State::Off }, Signal::Low) => {
self.module_type = ModuleType::FlipFlop { state: State::On };
self.outputs
.iter()
.map(|o| (self.id, *o, Signal::High))
.collect()
}
(ModuleType::Conjunction { ref mut inputs }, pulse) => {
inputs.entry(from).and_modify(|s| *s = *pulse);
if inputs.values().all(|s| matches!(s, Signal::High)) {
self.outputs
.iter()
.map(|o| (self.id, *o, Signal::Low))
.collect()
} else {
self.outputs
.iter()
.map(|o| (self.id, *o, Signal::High))
.collect()
}
}
_ => {
vec![]
}
}
}
}
fn broadcaster(input: &str) -> IResult<&str, Module> {
let (input, id) = alpha1(input)?;
let (input, _) = tag(" -> ")(input)?;
let (input, outputs) = separated_list1(tag(", "), alpha1)(input)?;
Ok((
input,
Module {
id,
module_type: ModuleType::Broadcaster,
outputs,
},
))
}
fn flip_flop(input: &str) -> IResult<&str, Module> {
let (input, id) = preceded(nom::character::complete::char('%'), alpha1)(input)?;
let (input, _) = tag(" -> ")(input)?;
let (input, outputs) = separated_list1(tag(", "), alpha1)(input)?;
Ok((
input,
Module {
id,
module_type: ModuleType::FlipFlop { state: State::Off },
outputs,
},
))
}
fn conjunction(input: &str) -> IResult<&str, Module> {
let (input, id) = preceded(nom::character::complete::char('&'), alpha1)(input)?;
let (input, _) = tag(" -> ")(input)?;
let (input, outputs) = separated_list1(tag(", "), alpha1)(input)?;
Ok((
input,
Module {
id,
module_type: ModuleType::Conjunction {
inputs: HashMap::new(),
},
outputs,
},
))
}
fn parser(input: &str) -> IResult<&str, HashMap<&str, Module>> {
let (input, modules) =
separated_list1(line_ending, alt((broadcaster, flip_flop, conjunction)))(input)?;
let mut module_map = modules
.into_iter()
.map(|m| (m.id, m))
.collect::<HashMap<&str, Module>>();
let conjunctions: Vec<&str> = module_map
.iter()
.filter(|(_, m)| matches!(m.module_type, ModuleType::Conjunction { .. }))
.map(|(id, _)| *id)
.collect();
let all_inputs: HashSet<&str> = module_map.keys().copied().collect();
let all_outputs: HashSet<&str> = module_map
.iter()
.flat_map(|(_, m)| m.outputs.iter().copied())
.collect();
for module in all_outputs.difference(&all_inputs) {
module_map.entry(module).or_insert(Module {
id: module,
module_type: ModuleType::Test,
outputs: Vec::default(),
});
}
for conjunction in conjunctions {
let inputs: Vec<&str> = module_map
.iter()
.filter(|(_, m)| m.outputs.contains(&conjunction))
.map(|(id, _)| *id)
.collect();
for input in inputs {
module_map.entry(conjunction).and_modify(|m| {
if let ModuleType::Conjunction { inputs, .. } = &mut m.module_type {
inputs.insert(input, Signal::Low);
}
});
}
}
Ok((input, module_map))
}
#[tracing::instrument(skip(input))]
pub fn process(input: &str) -> miette::Result<u64, AocError> {
let (input, mut modules) = parser(input).unwrap();
debug_assert!(input.is_empty());
let mut low_signals = 0;
let mut high_signals = 0;
for _ in 0..1000
// For each button press
{
let mut queue = VecDeque::new();
queue.push_back(("button", "broadcaster", Signal::Low));
low_signals += 1; // From initial button press
loop {
if queue.is_empty() {
break;
}
let (from, to, signal) = queue.pop_front().unwrap();
tracing::info!("{} -{:?}-> {}", from, signal, to);
let module = modules.get_mut(to).unwrap();
let signals = module.process(from, &signal);
signals.iter().for_each(|(_, _, s)| match s {
Signal::High => high_signals += 1,
Signal::Low => low_signals += 1,
});
queue.extend(signals);
}
}
Ok(low_signals * high_signals)
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[test_log::test(rstest)]
#[case("test-input.txt", 32000000)]
#[case("test-input2.txt", 11687500)]
fn test_process(#[case] filename: &str, #[case] expected: u64) -> miette::Result<()> {
let input =
String::from_utf8_lossy(&std::fs::read(std::path::Path::new(filename)).unwrap())
.parse::<String>()
.unwrap();
assert_eq!(expected, process(&input)?);
Ok(())
}
}
+18
View File
@@ -0,0 +1,18 @@
use crate::error::AocError;
#[tracing::instrument]
pub fn process(input: &str) -> miette::Result<u64, AocError> {
Ok(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test_log::test]
fn test_process() -> miette::Result<()> {
let input = include_str!("../test-input.txt");
assert_eq!(0, process(input)?);
Ok(())
}
}
+5
View File
@@ -0,0 +1,5 @@
broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a
+5
View File
@@ -0,0 +1,5 @@
broadcaster -> a
%a -> inv, con
&inv -> b
%b -> con
&con -> output