mirror of
https://github.com/anthonyoteri/advent-of-code-2023.git
synced 2026-06-05 18: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