Skip to content

Commit

Permalink
Day 20: Pulse Propagation
Browse files Browse the repository at this point in the history
  • Loading branch information
ephemient committed Dec 20, 2023
1 parent 93e15c5 commit d5212fc
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ Development occurs in language-specific directories:
|[Day17.hs](hs/src/Day17.hs)|[Day17.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day17.kt)|[day17.py](py/aoc2023/day17.py)|[day17.rs](rs/src/day17.rs)|
|[Day18.hs](hs/src/Day18.hs)|[Day18.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day18.kt)|[day18.py](py/aoc2023/day18.py)|[day18.rs](rs/src/day18.rs)|
|[Day19.hs](hs/src/Day19.hs)|[Day19.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day19.kt)|[day19.py](py/aoc2023/day19.py)|[day19.rs](rs/src/day19.rs)|
|[Day20.hs](hs/src/Day20.hs)|[Day20.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day20.kt)|[day20.py](py/aoc2023/day20.py)||
|[Day20.hs](hs/src/Day20.hs)|[Day20.kt](kt/aoc2023-lib/src/commonMain/kotlin/com/github/ephemient/aoc2023/Day20.kt)|[day20.py](py/aoc2023/day20.py)|[day20.rs](rs/src/day20.rs)|
10 changes: 8 additions & 2 deletions rs/benches/criterion.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use aoc2023::{
day1, day10, day11, day12, day13, day14, day15, day16, day17, day18, day19, day2, day3, day4,
day5, day6, day7, day8, day9,
day1, day10, day11, day12, day13, day14, day15, day16, day17, day18, day19, day2, day20, day3,
day4, day5, day6, day7, day8, day9,
};
use criterion::{black_box, Criterion};
use std::env;
Expand Down Expand Up @@ -131,6 +131,12 @@ fn aoc2023_bench(c: &mut Criterion) -> io::Result<()> {
g.bench_function("part 2", |b| b.iter(|| day19::part2(black_box(&data))));
g.finish();

let data = get_day_input(20)?;
let mut g = c.benchmark_group("day 20");
g.bench_function("part 1", |b| b.iter(|| day20::part1(black_box(&data))));
g.bench_function("part 2", |b| b.iter(|| day20::part2(black_box(&data))));
g.finish();

Ok(())
}

Expand Down
220 changes: 220 additions & 0 deletions rs/src/day20.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
use num_integer::lcm;
use std::collections::{HashMap, HashSet, VecDeque};

type Machines<'a> = HashMap<&'a str, (Option<Type>, HashSet<&'a str>)>;
type Dependencies<'a> = HashMap<&'a str, HashSet<&'a str>>;

#[derive(Clone, Copy, Debug)]
enum Type {
FlipFlop,
Conjunction,
}

#[derive(Clone, Debug)]
enum State<'a> {
FlipFlop(bool),
Conjunction(HashMap<&'a str, bool>),
}

impl<'a> State<'a> {
fn new(key: &'a str, type_: Type, dependencies: &Dependencies<'a>) -> State<'a> {
match type_ {
Type::FlipFlop => Self::FlipFlop(false),
Type::Conjunction => Self::Conjunction(dependencies.get(&key).map_or_else(
|| [].into(),
|vec| vec.iter().map(|&key| (key, false)).collect(),
)),
}
}

fn pulse(&mut self, key: &'a str, value: bool) -> Option<bool> {
match self {
State::FlipFlop(flip_flop) => {
if value {
None
} else {
*flip_flop = !*flip_flop;
Some(*flip_flop)
}
}
State::Conjunction(remembered) => {
*remembered.get_mut(key)? = value;
Some(!remembered.iter().all(|(_, &value)| value))
}
}
}

fn value(&self) -> bool {
match self {
State::FlipFlop(value) => *value,
State::Conjunction(remembered) => !remembered.iter().all(|(_, &value)| value),
}
}
}

fn parse(data: &str) -> Option<(Machines, Dependencies)> {
let mut machines = HashMap::new();
let mut dependencies = HashMap::new();
for line in data.lines() {
let Some((lhs, rhs)) = line.split_once(" -> ") else {
continue;
};
let (key, type_) = if let Some(key) = lhs.strip_prefix('%') {
(key, Some(Type::FlipFlop))
} else if let Some(key) = lhs.strip_prefix('&') {
(key, Some(Type::Conjunction))
} else {
(lhs, None)
};
let rhs = rhs.split(", ").collect();
for &dst in &rhs {
dependencies
.entry(dst)
.or_insert_with(HashSet::new)
.insert(key);
}
machines.insert(key, (type_, rhs));
}
Some((machines, dependencies))
}

pub fn part1(data: &str) -> Option<u32> {
let (machines, dependencies) = parse(data)?;
let mut state = machines
.iter()
.filter_map(|(&key, (type_, _))| Some((key, State::new(key, (*type_)?, &dependencies))))
.collect::<HashMap<_, _>>();
let (mut x, mut y) = (0, 0);
for _ in 0..1000 {
let mut queue: VecDeque<_> = [("button", "broadcaster", false)].into();
while let Some((src, key, value)) = queue.pop_front() {
if value {
x += 1;
} else {
y += 1
}
let Some(value) = (match state.get_mut(key) {
Some(state) => state.pulse(src, value),
None => Some(value),
}) else {
continue;
};
let Some((_, dsts)) = machines.get(key) else {
continue;
};
queue.extend(dsts.iter().map(|&dst| (key, dst, value)));
}
}
Some(x * y)
}

pub fn part2(data: &str) -> Option<usize> {
let (machines, dependencies) = parse(data)?;
let subsets = dependencies
.get({
let mut iter = dependencies.get("rx")?.iter();
let &conjunction = iter.next()?;
if iter.next().is_some() {
return None;
}
conjunction
})?
.iter()
.map(|&dst| {
let mut seen = HashSet::new();
let mut stack = vec![dst];
while let Some(key) = stack.pop() {
if !seen.insert(key) {
continue;
}
if let Some(keys) = dependencies.get(key) {
stack.extend(keys);
};
}
(dst, seen)
})
.collect::<Vec<_>>();
for (i, (_, s0)) in subsets.iter().enumerate() {
for (_, s1) in &subsets[i + 1..] {
let mut iter = s0.intersection(s1);
if !iter.next()?.eq(&"broadcaster") || iter.next().is_some() {
return None;
}
}
}
subsets.into_iter().try_fold(1, |acc, (_, subset)| {
let mut state = machines
.iter()
.filter_map(|(&key, (type_, _))| {
if subset.contains(key) {
Some((key, State::new(key, (*type_)?, &dependencies)))
} else {
None
}
})
.collect::<HashMap<_, _>>();
let mut seen = HashMap::new();
let size = loop {
let mut snapshot = state
.iter()
.map(|(&key, value)| (key, value.value()))
.collect::<Vec<_>>();
snapshot.sort_by(|(a, _), (b, _)| a.cmp(b));
let i = seen.len();
match seen.entry(snapshot) {
std::collections::hash_map::Entry::Occupied(entry) => {
break Some(i - *entry.get());
}
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(i);
}
}
let mut queue: VecDeque<_> = [("button", "broadcaster", false)].into();
while let Some((src, key, value)) = queue.pop_front() {
let Some(value) = (match state.get_mut(key) {
Some(state) => state.pulse(src, value),
None => Some(value),
}) else {
continue;
};
let Some((_, dsts)) = machines.get(key) else {
continue;
};
queue.extend(dsts.intersection(&subset).map(|&dst| (key, dst, value)));
}
}?;
Some(lcm(acc, size))
})
}

#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
use pretty_assertions::assert_eq;

static EXAMPLE_1: &str = indoc! {"
broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a
"};
static EXAMPLE_2: &str = indoc! {"
broadcaster -> a
%a -> inv, con
&inv -> b
%b -> con
&con -> output
"};

#[test]
fn part1_examples() {
assert_eq!(Some(32000000), part1(EXAMPLE_1));
assert_eq!(Some(11687500), part1(EXAMPLE_2));
}

#[test]
#[ignore]
fn part2_examples() {}
}
1 change: 1 addition & 0 deletions rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod day17;
pub mod day18;
pub mod day19;
pub mod day2;
pub mod day20;
pub mod day3;
pub mod day4;
pub mod day5;
Expand Down
12 changes: 10 additions & 2 deletions rs/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use aoc2023::{
day1, day10, day11, day12, day13, day14, day15, day16, day17, day18, day19, day2, day3, day4,
day5, day6, day7, day8, day9,
day1, day10, day11, day12, day13, day14, day15, day16, day17, day18, day19, day2, day20, day3,
day4, day5, day6, day7, day8, day9,
};
use std::collections::HashSet;
use std::env;
Expand Down Expand Up @@ -171,5 +171,13 @@ fn main() -> io::Result<()> {
println!();
}

if args.is_empty() || args.contains("20") {
println!("Day 20");
let data = get_day_input(20)?;
println!("{:?}", day20::part1(&data).expect("error"));
println!("{:?}", day20::part2(&data).expect("error"));
println!();
}

Ok(())
}

0 comments on commit d5212fc

Please sign in to comment.