mirror of
https://github.com/anthonyoteri/advent-of-code-2023.git
synced 2026-06-05 17:46:54 -04:00
+275
-7
@@ -1,18 +1,286 @@
|
||||
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;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn process(input: &str) -> miette::Result<u64, AocError> {
|
||||
Ok(0)
|
||||
#[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 target = modules
|
||||
.iter()
|
||||
.filter_map(|(id, m)| match m.module_type {
|
||||
ModuleType::Test => Some(*id),
|
||||
_ => None,
|
||||
})
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
let target_parents = modules
|
||||
.iter()
|
||||
.filter_map(|(id, m)| {
|
||||
if m.outputs.contains(&target) {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
let mut target_grandparents = target_parents
|
||||
.iter()
|
||||
.flat_map(|p| {
|
||||
modules
|
||||
.iter()
|
||||
.filter_map(|(gp, m)| {
|
||||
if m.outputs.contains(p) {
|
||||
Some(*gp)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<&str>>()
|
||||
})
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
let mut loops: HashMap<&str, usize> = HashMap::default();
|
||||
|
||||
for i in 1.. {
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back(("button", "broadcaster", Signal::Low));
|
||||
|
||||
loop {
|
||||
if queue.is_empty() {
|
||||
break;
|
||||
}
|
||||
let (from, to, signal) = queue.pop_front().unwrap();
|
||||
if target_grandparents.contains(&to) && matches!(signal, Signal::Low) {
|
||||
loops.insert(to, i);
|
||||
target_grandparents.retain(|gp| *gp != to);
|
||||
}
|
||||
|
||||
tracing::info!("{} -{:?}-> {}", from, signal, to);
|
||||
let module = modules.get_mut(to).unwrap();
|
||||
let signals = module.process(from, &signal);
|
||||
queue.extend(signals);
|
||||
}
|
||||
|
||||
if target_grandparents.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dbg!(&loops);
|
||||
|
||||
Ok(lcm(&loops.values().copied().collect::<Vec<usize>>()) as u64)
|
||||
}
|
||||
|
||||
fn lcm(nums: &[usize]) -> usize {
|
||||
if nums.len() == 1 {
|
||||
return nums[0];
|
||||
}
|
||||
let a = nums[0];
|
||||
let b = lcm(&nums[1..]);
|
||||
a * b / gcd(a, b)
|
||||
}
|
||||
|
||||
fn gcd(a: usize, b: usize) -> usize {
|
||||
if b == 0 {
|
||||
return a;
|
||||
}
|
||||
gcd(b, a % b)
|
||||
}
|
||||
#[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)?);
|
||||
use rstest::rstest;
|
||||
|
||||
#[test_log::test(rstest)]
|
||||
#[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(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user