mirror of
https://github.com/anthonyoteri/advent-of-code-2023.git
synced 2026-06-05 19:46:54 -04:00
@@ -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 = []
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod error;
|
||||||
|
|
||||||
|
pub mod part1;
|
||||||
|
pub mod part2;
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
broadcaster -> a, b, c
|
||||||
|
%a -> b
|
||||||
|
%b -> c
|
||||||
|
%c -> inv
|
||||||
|
&inv -> a
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
broadcaster -> a
|
||||||
|
%a -> inv, con
|
||||||
|
&inv -> b
|
||||||
|
%b -> con
|
||||||
|
&con -> output
|
||||||
Reference in New Issue
Block a user