Skip to content

Commit

Permalink
add day20 part1
Browse files Browse the repository at this point in the history
  • Loading branch information
mirsella committed Dec 20, 2023
1 parent 125baa4 commit 0ea3375
Show file tree
Hide file tree
Showing 7 changed files with 613 additions and 1 deletion.
2 changes: 1 addition & 1 deletion 2023/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ members = [
"day17",
"day18",
"day19",
# "day20",
"day20",
# "day21",
# "day22",
# "day23",
Expand Down
Empty file removed 2023/day19/map+sum
Empty file.
19 changes: 19 additions & 0 deletions 2023/day20/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
input.txt
flamegraph.svg
perf.data*
### Rust
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

10 changes: 10 additions & 0 deletions 2023/day20/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "day20"
authors = ["mirsella <[email protected]>"]
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

28 changes: 28 additions & 0 deletions 2023/day20/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::fs::File;
use std::io::{self, Read};
use std::path::PathBuf;
use std::{env, fs};

fn replace_in_file(file_path: &PathBuf, old: &str, new: &str) -> io::Result<()> {
let mut contents = String::new();
File::open(file_path)?.read_to_string(&mut contents)?;
let new_contents = contents.replace(old, new);
if contents != new_contents {
println!("Updating {}", file_path.display());
fs::write(file_path, new_contents)?;
}
Ok(())
}

fn main() -> io::Result<()> {
let pkg_name = env::var("CARGO_PKG_NAME").unwrap();
replace_in_file(
&"../Cargo.toml".into(),
&format!("# \"{pkg_name}\""),
&format!("\"{pkg_name}\""),
)?;

replace_in_file(&"./Cargo.toml".into(), "\n[workspace]", "")?;

Ok(())
}
260 changes: 260 additions & 0 deletions 2023/day20/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
use std::{
any::Any,
borrow::BorrowMut,
cell::{Cell, RefCell},
collections::{HashMap, VecDeque},
iter,
ops::{Add, Not},
rc::Rc,
};

#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
enum Pulse {
High,
Low,
}
impl Add<Pulse> for (usize, usize) {
type Output = (usize, usize);
fn add(self, pulse: Pulse) -> Self::Output {
match pulse {
Pulse::High => (self.0, self.1 + 1),
Pulse::Low => (self.0 + 1, self.1),
}
}
}
impl From<Pulse> for bool {
fn from(p: Pulse) -> Self {
match p {
Pulse::High => true,
Pulse::Low => false,
}
}
}
impl From<bool> for Pulse {
fn from(b: bool) -> Self {
match b {
true => Self::High,
false => Self::Low,
}
}
}
type Modules<'a> = Vec<Box<dyn Module<'a>>>;
type PulseResult<'a> = (Pulse, &'a [&'a str]);
trait Module<'a> {
fn pulse(&self, sender: &'a str, input: Pulse) -> PulseResult;
fn name(&self) -> &'a str;
fn as_any(&self) -> &(dyn Any + 'a);
}
struct Broadcaster<'a> {
outputs: Vec<&'a str>,
name: &'a str,
}
impl<'a> Module<'a> for Broadcaster<'a> {
fn pulse(&self, _: &str, input: Pulse) -> PulseResult {
(input, &self.outputs)
}
fn name(&self) -> &'a str {
self.name
}
fn as_any(&self) -> &(dyn Any + 'a) {
self
}
}
impl<'a> Broadcaster<'a> {
fn new(name: &'a str, outputs: Vec<&'a str>) -> Self {
Self { name, outputs }
}
}
struct Dummy<'a> {
name: &'a str,
}
impl<'a> Module<'a> for Dummy<'a> {
fn pulse(&self, _: &str, _input: Pulse) -> PulseResult {
(Pulse::Low, &[])
}
fn name(&self) -> &'a str {
self.name
}
fn as_any(&self) -> &(dyn Any + 'a) {
self
}
}
impl<'a> Dummy<'a> {
fn new(name: &'a str) -> Self {
Self { name }
}
}
struct Flipflop<'a> {
state: Cell<bool>,
outputs: Vec<&'a str>,
name: &'a str,
}
impl<'a> Module<'a> for Flipflop<'a> {
fn pulse(&self, _: &str, input: Pulse) -> PulseResult {
if let Pulse::High = input {
return (Pulse::Low, &[]);
}
match self.state.get() {
true => self.state.set(false),
false => self.state.set(true),
}
let state = self.state.get().into();
(state, &self.outputs)
}
fn name(&self) -> &'a str {
self.name
}
fn as_any(&self) -> &(dyn Any + 'a) {
self
}
}
impl<'a> Flipflop<'a> {
fn new(name: &'a str, outputs: Vec<&'a str>) -> Self {
Self {
name,
outputs,
state: Cell::new(false),
}
}
}
struct Conjunction<'a> {
name: &'a str,
inputs: RefCell<HashMap<&'a str, Pulse>>,
outputs: Vec<&'a str>,
}
impl<'a> Module<'a> for Conjunction<'a> {
fn pulse(&self, sender: &'a str, input: Pulse) -> PulseResult {
let all_high = {
self.inputs.borrow_mut().insert(sender, input);
self.inputs.borrow().values().all(|&p| p == Pulse::High)
// all high -> low
// mixed -> high
// all low -> high
};
let pulse = all_high.not().into();
(pulse, &self.outputs)
}
fn name(&self) -> &'a str {
self.name
}
fn as_any(&self) -> &(dyn Any + 'a) {
self
}
}
impl<'a> Conjunction<'a> {
fn new(name: &'a str, outputs: Vec<&'a str>, inputs: Vec<&'a str>) -> Self {
Self {
name,
inputs: RefCell::new(HashMap::from_iter(
inputs.into_iter().map(|i| (i, Pulse::Low)),
)),
outputs,
}
}
}

fn new_module<'a>(
name: &'a str,
outputs: Vec<&'a str>,
inputs: Vec<&'a str>,
) -> Box<dyn Module<'a> + 'a> {
if name == "broadcaster" {
return Box::new(Broadcaster::new(name, outputs));
}
if name.starts_with('%') {
return Box::new(Flipflop::new(name.strip_prefix('%').unwrap(), outputs));
}
if name.starts_with('&') {
return Box::new(Conjunction::new(
name.strip_prefix('&').unwrap(),
outputs,
inputs,
));
}
return Box::new(Dummy::new(name));
}

fn parse(input: &'static str) -> Vec<Box<dyn Module<'static> + 'static>> {
input
.lines()
.map(|line| {
let mut s = line.split_whitespace();
let name = s.next().unwrap().trim();

let outputs = s
.skip(1)
.map(|c| c.trim_matches([' ', ','].as_ref()))
.collect();

let inputs = input
.lines()
.filter_map(|line| {
let s = line.split_once("->").unwrap();
if !s.1.contains(name.trim_matches([' ', '&', '%'].as_ref())) {
return None;
}
Some(s.0.trim_matches([' ', '&', '%'].as_ref()))
})
.collect();
new_module(name, outputs, inputs)
})
.collect::<Vec<_>>()
}

fn press_button<'a>(modules: &Modules<'a>) -> (usize, usize) {
let mut queue: VecDeque<(&'a str, PulseResult)> = VecDeque::new();
let mut counter = (1, 0);
let broadcaster = modules.iter().find(|m| m.name() == "broadcaster").unwrap();
queue.push_back((broadcaster.name(), broadcaster.pulse("button", Pulse::Low)));
while let Some((name, (pulse, outputs))) = queue.pop_front() {
for output in outputs {
counter = counter + pulse;
if let Some(module) = modules.iter().find(|&m| m.name() == *output) {
queue.push_back((module.name(), module.pulse(name, pulse)));
}
}
}
counter
}

fn part1(input: &'static str) -> usize {
let modules = parse(input);
let count = (0..1000)
.map(|_| press_button(&modules))
.fold((0, 0), |a, b| (a.0 + b.0, a.1 + b.1));
count.0 * count.1
}
fn part2(input: &str) -> usize {
todo!()
}
fn main() {
let input: &str = include_str!("../input.txt");
println!("Part 1: {}", part1(input));
println!("Part 2: {}", part2(input));
}

#[cfg(test)]
mod tests {
const INPUT: &str = "broadcaster -> a, b, c
%a -> b
%b -> c
%c -> inv
&inv -> a";
const INPUT2: &str = "broadcaster -> a
%a -> inv, con
&inv -> b
%b -> con
&con -> output";
#[test]
fn part1() {
assert_eq!(super::part1(INPUT), 8000 * 4000);
}
#[test]
fn part1_2() {
assert_eq!(super::part1(INPUT2), 4250 * 2750);
}
#[test]
fn part2() {
assert_eq!(super::part2(INPUT), 0);
}
}
Loading

0 comments on commit 0ea3375

Please sign in to comment.