Compare commits
8 Commits
e3f0f932e4
...
main
Author | SHA1 | Date | |
---|---|---|---|
50472363cf | |||
2c4288de42 | |||
14c6da72d9 | |||
bdc150d0ed | |||
b802cf1d16 | |||
9db0e3f054 | |||
a19a3258c2 | |||
d3c11dcbed |
190
Cargo.lock
generated
190
Cargo.lock
generated
@@ -10,12 +10,24 @@ dependencies = [
|
||||
"aoc-runner-derive",
|
||||
"array2d",
|
||||
"itertools",
|
||||
"num",
|
||||
"prev-iter",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rust-crypto",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aoc-runner"
|
||||
version = "0.3.0"
|
||||
@@ -51,6 +63,18 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b39cb2c1bf5a7c0dd097aa95ab859cf87dab5a4328900f5388942dc1889f74"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
@@ -69,6 +93,17 @@ version = "0.3.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@@ -96,6 +131,94 @@ version = "0.2.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "prev-iter"
|
||||
version = "0.1.2"
|
||||
@@ -143,6 +266,27 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
@@ -158,6 +302,15 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
@@ -167,6 +320,35 @@ dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "rust-crypto"
|
||||
version = "0.2.36"
|
||||
@@ -277,7 +459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -293,6 +475,12 @@ version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
@@ -15,3 +15,6 @@ array2d = "0.3.0"
|
||||
strum = "0.25.0"
|
||||
strum_macros = "0.25"
|
||||
prev-iter = "0.1.2"
|
||||
regex = "1.10.2"
|
||||
num = "0.4.1"
|
||||
rand = "0.8.5"
|
||||
|
@@ -47,10 +47,10 @@ fn part2(input: &Vec<String>) -> usize {
|
||||
if let Some(position) = boxes[box_number].iter().position(|(l, _)| *l == *label) {
|
||||
let _ = std::mem::replace(
|
||||
&mut boxes[box_number][position],
|
||||
(label.clone().to_string(), *focal_length as usize),
|
||||
(label.to_string(), *focal_length as usize),
|
||||
);
|
||||
} else {
|
||||
boxes[box_number].push((label.clone().to_string(), *focal_length as usize));
|
||||
boxes[box_number].push((label.to_string(), *focal_length as usize));
|
||||
}
|
||||
} else {
|
||||
panic!("invalid step AHHH {}", step)
|
||||
|
245
src/day19.rs
Normal file
245
src/day19.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
use std::collections::HashMap;
|
||||
use regex::{Regex, Match};
|
||||
|
||||
enum Rule {
|
||||
ACCEPTED,
|
||||
REJECTED,
|
||||
GOTO(String),
|
||||
GT(String, usize, String),
|
||||
LT(String, usize, String),
|
||||
}
|
||||
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
#[aoc_generator(day19, part1)]
|
||||
fn parse(input: &str) -> (HashMap<String, Vec<Rule>>, Vec<HashMap<String, usize>>) {
|
||||
let (workflows_input, ratings_input) = input.split_once("\n\n").unwrap();
|
||||
let workflows = parse_workflows(workflows_input);
|
||||
|
||||
// screw it im using regex
|
||||
let rer = Regex::new(r"^\{x=(\d+),m=(\d+),a=(\d+),s=(\d+)\}$").unwrap();
|
||||
let ratings: Vec<HashMap<String, usize>> = ratings_input.lines().map(|line| {
|
||||
let caps = rer.captures(line).unwrap();
|
||||
let x = parse_usize(caps.get(1));
|
||||
let m = parse_usize(caps.get(2));
|
||||
let a = parse_usize(caps.get(3));
|
||||
let s = parse_usize(caps.get(4));
|
||||
|
||||
let mut vals = HashMap::new();
|
||||
vals.insert("x".to_string(), x);
|
||||
vals.insert("m".to_string(), m);
|
||||
vals.insert("a".to_string(), a);
|
||||
vals.insert("s".to_string(), s);
|
||||
vals
|
||||
}).collect();
|
||||
|
||||
(workflows, ratings)
|
||||
}
|
||||
|
||||
fn parse_usize(g: Option<Match>) -> usize {
|
||||
g.map_or(0, |m| m.as_str().parse().unwrap())
|
||||
}
|
||||
|
||||
fn parse_workflows(workflows_input: &str) -> HashMap<String, Vec<Rule>> {
|
||||
let mut workflows: HashMap<String, Vec<Rule>> = HashMap::new();
|
||||
let rew = Regex::new(r"^(.+)\{(.+)\}$").unwrap();
|
||||
workflows_input.lines().for_each(|line| {
|
||||
let caps = rew.captures(line).unwrap();
|
||||
let key = caps.get(1).unwrap().as_str();
|
||||
let rules_str = caps.get(2).unwrap().as_str();
|
||||
|
||||
let rules = rules_str.split(",").map(|expr| {
|
||||
if expr == "A" {
|
||||
return Rule::ACCEPTED;
|
||||
}
|
||||
if expr == "R" {
|
||||
return Rule::REJECTED;
|
||||
}
|
||||
if !expr.contains(":") {
|
||||
return Rule::GOTO(expr.to_string());
|
||||
}
|
||||
|
||||
let (rule, to) = expr.split_once(":").unwrap();
|
||||
|
||||
return if rule.contains(">") {
|
||||
let (prop, val) = rule.split_once(">").unwrap();
|
||||
Rule::GT(prop.to_string(), val.parse().unwrap(), to.to_string())
|
||||
} else if rule.contains("<") {
|
||||
let (prop, val) = rule.split_once("<").unwrap();
|
||||
Rule::LT(prop.to_string(), val.parse().unwrap(), to.to_string())
|
||||
} else {
|
||||
panic!("Unknown rule {}", rule)
|
||||
};
|
||||
}).collect();
|
||||
|
||||
workflows.insert(key.to_string(), rules);
|
||||
});
|
||||
workflows
|
||||
}
|
||||
|
||||
#[aoc(day19, part1)]
|
||||
fn part1((workflows, ratings): &(HashMap<String, Vec<Rule>>, Vec<HashMap<String, usize>>)) -> usize {
|
||||
let mut accepted: Vec<HashMap<String, usize>> = vec!();
|
||||
'outer: for rating in ratings {
|
||||
let mut wf_key = "in";
|
||||
'middle: loop {
|
||||
if wf_key == "A" {
|
||||
accepted.push(rating.clone());
|
||||
continue 'outer;
|
||||
} else if wf_key == "R" {
|
||||
continue 'outer;
|
||||
}
|
||||
|
||||
let rules = workflows.get(wf_key).unwrap();
|
||||
for rule in rules {
|
||||
match rule {
|
||||
Rule::ACCEPTED => {
|
||||
accepted.push(rating.clone());
|
||||
continue 'outer;
|
||||
}
|
||||
Rule::REJECTED => {
|
||||
continue 'outer;
|
||||
}
|
||||
Rule::GOTO(new_wf_key) => {
|
||||
wf_key = new_wf_key;
|
||||
continue 'middle;
|
||||
}
|
||||
Rule::GT(prop, val, to) => {
|
||||
if rating.get(prop.as_str()).unwrap() > val {
|
||||
wf_key = to;
|
||||
continue 'middle;
|
||||
}
|
||||
}
|
||||
Rule::LT(prop, val, to) => {
|
||||
if rating.get(prop.as_str()).unwrap() < val {
|
||||
wf_key = to;
|
||||
continue 'middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accepted.iter().map(|rating| rating.values().sum::<usize>()).sum::<usize>()
|
||||
}
|
||||
|
||||
|
||||
#[aoc_generator(day19, part2)]
|
||||
fn parse_part2(input: &str) -> HashMap<String, Vec<Rule>> {
|
||||
let (workflows_input, _) = input.split_once("\n\n").unwrap();
|
||||
parse_workflows(workflows_input)
|
||||
}
|
||||
|
||||
#[aoc(day19, part2)]
|
||||
fn part2(workflows: &HashMap<String, Vec<Rule>>) -> usize {
|
||||
let mut stack: Vec<((usize, usize), (usize, usize), (usize, usize), (usize, usize), &str, usize)> =
|
||||
vec! {((1, 4000), (1, 4000), (1, 4000), (1, 4000), "in", 0)};
|
||||
let mut accepted: Vec<((usize, usize), (usize, usize), (usize, usize), (usize, usize))> = vec!();
|
||||
while let Some(range) = stack.pop() {
|
||||
let (x, m, a, s, wf_key, rule_key) = range;
|
||||
if wf_key == "A" {
|
||||
accepted.push((x, m, a, s));
|
||||
continue;
|
||||
} else if wf_key == "R" {
|
||||
continue;
|
||||
}
|
||||
|
||||
if x.0 > x.1 || m.0 > m.1 || a.0 > a.1 || s.0 > s.1 { continue }
|
||||
|
||||
let rules = workflows.get(wf_key).unwrap();
|
||||
let rule = &rules[rule_key];
|
||||
match rule {
|
||||
Rule::ACCEPTED => {
|
||||
accepted.push((x, m, a, s));
|
||||
continue;
|
||||
}
|
||||
Rule::REJECTED => {
|
||||
continue;
|
||||
}
|
||||
Rule::GOTO(new_wf_key) => {
|
||||
stack.push((x, m, a, s, new_wf_key, 0));
|
||||
continue;
|
||||
}
|
||||
Rule::GT(prop, val, to) => {
|
||||
match prop.as_str() {
|
||||
"x" => {
|
||||
stack.push(((val + 1, x.1), m, a, s, to.as_str(), 0));
|
||||
stack.push(((x.0, *val), m, a, s, wf_key, rule_key + 1));
|
||||
}
|
||||
"m" => {
|
||||
stack.push((x, (val + 1, m.1), a, s, to.as_str(), 0));
|
||||
stack.push((x, (m.0, *val), a, s, wf_key, rule_key + 1));
|
||||
}
|
||||
"a" => {
|
||||
stack.push((x, m, (val + 1, a.1), s, to.as_str(), 0));
|
||||
stack.push((x, m, (a.0, *val), s, wf_key, rule_key + 1));
|
||||
}
|
||||
"s" => {
|
||||
stack.push((x, m, a, (val + 1, s.1), to.as_str(), 0));
|
||||
stack.push((x, m, a, (s.0, *val), wf_key, rule_key + 1));
|
||||
}
|
||||
_ => { panic!("unknown prop {}", prop) }
|
||||
}
|
||||
}
|
||||
Rule::LT(prop, val, to) => {
|
||||
match prop.as_str() {
|
||||
"x" => {
|
||||
stack.push(((x.0, val - 1), m, a, s, to.as_str(), 0));
|
||||
stack.push(((*val, x.1), m, a, s, wf_key, rule_key + 1));
|
||||
}
|
||||
"m" => {
|
||||
stack.push((x, (m.0, val - 1), a, s, to.as_str(), 0));
|
||||
stack.push((x, (*val, m.1), a, s, wf_key, rule_key + 1));
|
||||
}
|
||||
"a" => {
|
||||
stack.push((x, m, (a.0, val - 1), s, to.as_str(), 0));
|
||||
stack.push((x, m, (*val, a.1), s, wf_key, rule_key + 1));
|
||||
}
|
||||
"s" => {
|
||||
stack.push((x, m, a, (s.0, val - 1), to.as_str(), 0));
|
||||
stack.push((x, m, a, (*val, s.1), wf_key, rule_key + 1));
|
||||
}
|
||||
_ => { panic!("unknown prop {}", prop) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accepted.iter().map(|(x, m, a, s)| {
|
||||
(x.1 - x.0 + 1) * (m.1 - m.0 + 1) * (a.1 - a.0 + 1) * (s.1 - s.0 + 1)
|
||||
}).sum()
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const EX: &str = r"px{a<2006:qkq,m>2090:A,rfg}
|
||||
pv{a>1716:R,A}
|
||||
lnx{m>1548:A,A}
|
||||
rfg{s<537:gd,x>2440:R,A}
|
||||
qs{s>3448:A,lnx}
|
||||
qkq{x<1416:A,crn}
|
||||
crn{x>2662:A,R}
|
||||
in{s<1351:px,qqz}
|
||||
qqz{s>2770:qs,m<1801:hdj,R}
|
||||
gd{a>3333:R,R}
|
||||
hdj{m>838:A,pv}
|
||||
|
||||
{x=787,m=2655,a=1222,s=2876}
|
||||
{x=1679,m=44,a=2067,s=496}
|
||||
{x=2036,m=264,a=79,s=2244}
|
||||
{x=2461,m=1339,a=466,s=291}
|
||||
{x=2127,m=1623,a=2188,s=1013}";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&parse(EX)), 19114);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&parse_part2(EX)), 167409079868000);
|
||||
}
|
||||
}
|
219
src/day20.rs
Normal file
219
src/day20.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use num::Integer;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Module {
|
||||
FlipFlop { state: bool, dests: Vec<String> },
|
||||
Conjunction { state: HashMap<String, bool>, dests: Vec<String> },
|
||||
Broadcast { dests: Vec<String> },
|
||||
}
|
||||
|
||||
#[aoc_generator(day20)]
|
||||
fn parse(input: &str) -> HashMap<String, Module> {
|
||||
let mut input: HashMap<_, _> = input.lines().map(|line| {
|
||||
let (name, dests) = line.split_once("->").unwrap();
|
||||
let dests = parse_destinations(dests);
|
||||
let module = parse_module(name.trim(), dests);
|
||||
(name.trim_matches(['&', '%', ' '].as_slice()).to_string(), module)
|
||||
}).collect();
|
||||
collect_inputs(&mut input);
|
||||
input
|
||||
}
|
||||
|
||||
fn parse_module(name: &str, dests: Vec<String>) -> Module {
|
||||
match name {
|
||||
"broadcaster" => Module::Broadcast { dests },
|
||||
name if name.starts_with('%') => Module::FlipFlop { state: false, dests },
|
||||
name if name.starts_with('&') => Module::Conjunction { state: HashMap::new(), dests },
|
||||
_ => panic!("Invalid Module {}", name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn collect_inputs(modules: &mut HashMap<String, Module>) {
|
||||
for (name, module) in modules.clone() {
|
||||
let dests = match module {
|
||||
Module::FlipFlop { state: _, dests: dest } => dest,
|
||||
Module::Conjunction { state: _, dests: dest } => dest,
|
||||
Module::Broadcast { dests: dest } => dest,
|
||||
};
|
||||
|
||||
for dest_name in dests {
|
||||
if let Some(dest_mod) = modules.get_mut(&dest_name) {
|
||||
match dest_mod {
|
||||
Module::Conjunction { state, dests: _ } => state.insert(name.to_string(), false),
|
||||
_ => continue
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_destinations(dests: &str) -> Vec<String> {
|
||||
dests.split(',')
|
||||
.map(|x| x.trim().to_string())
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
#[aoc(day20, part1)]
|
||||
fn part1(modules: &HashMap<String, Module>) -> usize {
|
||||
let mut modules = modules.clone();
|
||||
let mut high: usize = 0;
|
||||
let mut low: usize = 0;
|
||||
for _ in 0..1000 {
|
||||
let (high_pulses, low_pulses) = push_the_button(&mut modules);
|
||||
high += high_pulses;
|
||||
low += low_pulses;
|
||||
}
|
||||
|
||||
high * low
|
||||
}
|
||||
|
||||
fn push_the_button(modules: &mut HashMap<String, Module>) -> (usize, usize) {
|
||||
let mut process_queue: VecDeque<(String, bool, String)> = VecDeque::new();
|
||||
process_queue.push_back(("broadcaster".to_string(), false, "".to_string()));
|
||||
let mut low_pulses: usize = 0;
|
||||
let mut high_pulses: usize = 0;
|
||||
|
||||
while let Some((module_name, input, sender)) = process_queue.pop_front() {
|
||||
match input {
|
||||
true => high_pulses += 1,
|
||||
false => low_pulses += 1,
|
||||
}
|
||||
if let Some(module) = modules.get_mut(&module_name) {
|
||||
match module {
|
||||
Module::FlipFlop { state, dests } => {
|
||||
if !input {
|
||||
let flipped = !(*state);
|
||||
*state = flipped;
|
||||
for dest in dests {
|
||||
process_queue.push_back((dest.clone(), state.clone(), module_name.clone()));
|
||||
}
|
||||
}
|
||||
},
|
||||
Module::Conjunction { state, dests } => {
|
||||
let stored = state.get_mut(&sender.to_string()).unwrap();
|
||||
*stored = input;
|
||||
|
||||
let mut send = true;
|
||||
if state.values().all(|x| *x) {
|
||||
send = false;
|
||||
}
|
||||
for dest in dests {
|
||||
process_queue.push_back((dest.clone(), send, module_name.clone()));
|
||||
}
|
||||
},
|
||||
Module::Broadcast { dests } => {
|
||||
for dest in dests {
|
||||
process_queue.push_back((dest.clone(), input, module_name.clone()));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (high_pulses, low_pulses)
|
||||
}
|
||||
|
||||
#[aoc(day20, part2)]
|
||||
fn part2(og_modules: &HashMap<String, Module>) -> usize {
|
||||
let mut lcm = vec![];
|
||||
let before = ["tr", "xm", "dr", "nh"];
|
||||
|
||||
for module_name in before {
|
||||
let mut modules = og_modules.clone();
|
||||
let mut count: usize = 1;
|
||||
while !push_the_button_part2(&mut modules, module_name.to_string()) {
|
||||
count += 1;
|
||||
}
|
||||
lcm.push(count);
|
||||
}
|
||||
|
||||
println!("{:?}", lcm);
|
||||
|
||||
let lcm = lcm.iter()
|
||||
.cloned()
|
||||
.reduce(|a, b| a.lcm(&b))
|
||||
.unwrap();
|
||||
|
||||
lcm
|
||||
}
|
||||
|
||||
fn push_the_button_part2(modules: &mut HashMap<String, Module>, to_find: String) -> bool {
|
||||
let mut process_queue: VecDeque<(String, bool, String)> = VecDeque::new();
|
||||
process_queue.push_back(("broadcaster".to_string(), false, "".to_string()));
|
||||
|
||||
while let Some((module_name, input, sender)) = process_queue.pop_front() {
|
||||
if module_name == to_find && input == false {
|
||||
return true
|
||||
}
|
||||
if let Some(module) = modules.get_mut(&module_name) {
|
||||
match module {
|
||||
Module::FlipFlop { state, dests } => {
|
||||
if !input {
|
||||
let flipped = !(*state);
|
||||
*state = flipped;
|
||||
for dest in dests {
|
||||
process_queue.push_back((dest.clone(), state.clone(), module_name.clone()));
|
||||
}
|
||||
}
|
||||
},
|
||||
Module::Conjunction { state, dests } => {
|
||||
let stored = state.get_mut(&sender.to_string()).unwrap();
|
||||
*stored = input;
|
||||
|
||||
let mut send = true;
|
||||
if state.values().all(|x| *x) {
|
||||
send = false;
|
||||
}
|
||||
for dest in dests {
|
||||
process_queue.push_back((dest.clone(), send, module_name.clone()));
|
||||
}
|
||||
},
|
||||
Module::Broadcast { dests } => {
|
||||
for dest in dests {
|
||||
process_queue.push_back((dest.clone(), input, module_name.clone()));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const EX_1: &str = r"broadcaster -> a, b, c
|
||||
%a -> b
|
||||
%b -> c
|
||||
%c -> inv
|
||||
&inv -> a";
|
||||
|
||||
const EX_2: &str = r"broadcaster -> a
|
||||
%a -> inv, con
|
||||
&inv -> b
|
||||
%b -> con
|
||||
&con -> rx";
|
||||
|
||||
#[test]
|
||||
fn part1_example1() {
|
||||
assert_eq!(part1(&parse(EX_1)), 32000000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn part1_example2() {
|
||||
assert_eq!(part1(&parse(EX_2)), 11687500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&parse(EX_2)), 1);
|
||||
}
|
||||
}
|
186
src/day21.rs
Normal file
186
src/day21.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use std::collections::HashSet;
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
|
||||
const STEPS: i32 = 64;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
struct Position(i32, i32);
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Garden {
|
||||
rocks: HashSet<Position>,
|
||||
start: Position,
|
||||
extents: (Position, Position),
|
||||
}
|
||||
|
||||
#[aoc_generator(day21, part1)]
|
||||
fn parse(input: &str) -> Garden {
|
||||
let mut rocks: HashSet<Position> = HashSet::new();
|
||||
let mut start: Position = Position(0, 0);
|
||||
for (row, line) in input.lines().enumerate() {
|
||||
for (col, ch) in line.chars().enumerate() {
|
||||
match ch {
|
||||
'#' => {
|
||||
rocks.insert(Position(row as i32, col as i32));
|
||||
}
|
||||
'S' => {
|
||||
start = Position(row as i32, col as i32);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
let mut trans_rocks: HashSet<Position> = HashSet::new();
|
||||
for rock in rocks.iter() {
|
||||
let trans_rock = Position(rock.0 - start.0, start.1 - rock.1);
|
||||
trans_rocks.insert(trans_rock);
|
||||
}
|
||||
rocks = trans_rocks;
|
||||
let num_rows = input.lines().count() as i32;
|
||||
let num_cols = input.lines().next().unwrap().chars().count() as i32;
|
||||
let extents = (
|
||||
Position(-num_rows / 2, -num_cols / 2),
|
||||
Position(num_rows / 2, num_cols / 2),
|
||||
);
|
||||
Garden {
|
||||
rocks,
|
||||
start: Position(0, 0),
|
||||
extents,
|
||||
}
|
||||
}
|
||||
|
||||
#[aoc(day21, part1)]
|
||||
fn part1(map: &Garden) -> u32 {
|
||||
let mut visited: HashSet<Position> = HashSet::new();
|
||||
visited.insert(map.start);
|
||||
for _ in 0..STEPS {
|
||||
let mut new_visited = HashSet::new();
|
||||
for pos in visited.iter().clone() {
|
||||
let neighbors = [
|
||||
Position(pos.0 - 1, pos.1),
|
||||
Position(pos.0 + 1, pos.1),
|
||||
Position(pos.0, pos.1 - 1),
|
||||
Position(pos.0, pos.1 + 1),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|p| in_bounds(*p, map.extents) && !map.rocks.contains(p));
|
||||
for neighbor in neighbors {
|
||||
new_visited.insert(neighbor);
|
||||
}
|
||||
}
|
||||
visited = new_visited;
|
||||
}
|
||||
visited.len() as u32
|
||||
}
|
||||
|
||||
fn in_bounds(pos: Position, extents: (Position, Position)) -> bool {
|
||||
pos.0 >= extents.0 .0 && pos.0 <= extents.1 .0 && pos.1 >= extents.0 .1 && pos.1 < extents.1 .1
|
||||
}
|
||||
|
||||
fn in_bounds_part2(pos: Position, extents: Position) -> bool {
|
||||
pos.0 >= 0 && pos.0 <= extents.0 && pos.1 >= 0 && pos.1 < extents.1
|
||||
}
|
||||
|
||||
struct GardenPart2 {
|
||||
rocks: HashSet<Position>,
|
||||
start: Position,
|
||||
extents: Position,
|
||||
}
|
||||
|
||||
#[aoc_generator(day21, part2)]
|
||||
fn parse_part2(input: &str) -> GardenPart2 {
|
||||
let mut rocks: HashSet<Position> = HashSet::new();
|
||||
for (row, line) in input.lines().enumerate() {
|
||||
for (col, ch) in line.chars().enumerate() {
|
||||
match ch {
|
||||
'#' => {
|
||||
rocks.insert(Position(row as i32, col as i32));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
let num_rows = input.lines().count() as i32;
|
||||
let num_cols = input.lines().next().unwrap().chars().count() as i32;
|
||||
let mut expanded_rocks: HashSet<Position> = HashSet::new();
|
||||
for rock in rocks.iter() {
|
||||
for row_mul in 0..5 {
|
||||
for col_mul in 0..5 {
|
||||
let expanded_rock = Position(
|
||||
rock.0 + (num_rows * row_mul as i32),
|
||||
rock.1 + (num_cols * col_mul as i32),
|
||||
);
|
||||
expanded_rocks.insert(expanded_rock);
|
||||
}
|
||||
}
|
||||
}
|
||||
GardenPart2 {
|
||||
rocks: expanded_rocks,
|
||||
start: Position(num_rows * 5 / 2, num_cols * 5 / 2),
|
||||
extents: Position(num_rows * 5, num_cols * 5),
|
||||
}
|
||||
}
|
||||
|
||||
#[aoc(day21, part2)]
|
||||
fn part2(map: &GardenPart2) -> i64 {
|
||||
let b0: i64 = walk(&map, 65) as i64;
|
||||
let b1: i64 = walk(&map, 65 + 131) as i64;
|
||||
let b2: i64 = walk(&map, 65 + 2 * 131) as i64;
|
||||
let n: i64 = 202300;
|
||||
// below uses Cramer's Rule to solve for x0, x1, x2
|
||||
let det_a: f64 = -2.0;
|
||||
let det_a0: f64 = -b0 as f64 + 2.0 * b1 as f64 - b2 as f64;
|
||||
let det_a1: f64 = 3.0 * b0 as f64 - 4.0 * b1 as f64 + b2 as f64;
|
||||
let det_a2: f64 = -2.0 * b0 as f64;
|
||||
let x0: i64 = (det_a0 / det_a) as i64;
|
||||
let x1: i64 = (det_a1 / det_a) as i64;
|
||||
let x2: i64 = (det_a2 / det_a) as i64;
|
||||
|
||||
x0 * n * n + x1 * n + x2
|
||||
}
|
||||
|
||||
fn walk(garden: &GardenPart2, steps: u32) -> u32 {
|
||||
let mut visited: HashSet<Position> = HashSet::new();
|
||||
visited.insert(garden.start);
|
||||
for _ in 0..steps {
|
||||
let mut new_visited = HashSet::new();
|
||||
for pos in visited.iter().clone() {
|
||||
let neighbors = [
|
||||
Position(pos.0 - 1, pos.1),
|
||||
Position(pos.0 + 1, pos.1),
|
||||
Position(pos.0, pos.1 - 1),
|
||||
Position(pos.0, pos.1 + 1),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|p| in_bounds_part2(*p, garden.extents) && !garden.rocks.contains(p));
|
||||
for neighbor in neighbors {
|
||||
new_visited.insert(neighbor);
|
||||
}
|
||||
}
|
||||
visited = new_visited;
|
||||
}
|
||||
visited.len() as u32
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const EX: &str = r"...........
|
||||
.....###.#.
|
||||
.###.##..#.
|
||||
..#.#...#..
|
||||
....#.#....
|
||||
.##..S####.
|
||||
.##..#...#.
|
||||
.......##..
|
||||
.##.#.####.
|
||||
.##..##.##.
|
||||
...........";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&parse(EX)), 16);
|
||||
}
|
||||
}
|
133
src/day22.rs
Normal file
133
src/day22.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
use regex::Regex;
|
||||
|
||||
pub struct Brick {
|
||||
up: Vec<Vec<usize>>,
|
||||
down: Vec<Vec<usize>>,
|
||||
}
|
||||
|
||||
|
||||
#[aoc_generator(day22)]
|
||||
fn parse(input: &str) -> Brick {
|
||||
let re: Regex = Regex::new(r"\d+").unwrap();
|
||||
|
||||
let mut bricks: Vec<[usize; 6]> = input.lines().map(|line| {
|
||||
re.captures_iter(line)
|
||||
.map(|c| c.extract::<0>().0.parse::<usize>().unwrap())
|
||||
.collect::<Vec<_>>().try_into().unwrap()
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
let mut heights = [[0; 10]; 10];
|
||||
let mut indices = [[usize::MAX; 10]; 10];
|
||||
let mut up = vec![Vec::new(); bricks.len()];
|
||||
let mut down = vec![Vec::new(); bricks.len()];
|
||||
|
||||
bricks.sort_unstable_by_key(|b| b[2]);
|
||||
|
||||
for (i, &[x1, y1, z1, x2, y2, z2]) in bricks.iter().enumerate() {
|
||||
let height = z2 - z1 + 1;
|
||||
let mut top = 0;
|
||||
let mut previous = usize::MAX;
|
||||
|
||||
for x in x1..=x2 {
|
||||
for y in y1..=y2 {
|
||||
top = top.max(heights[x][y]);
|
||||
}
|
||||
}
|
||||
|
||||
for x in x1..=x2 {
|
||||
for y in y1..=y2 {
|
||||
if heights[x][y] == top {
|
||||
let index = indices[x][y];
|
||||
if index != previous {
|
||||
up[index].push(i);
|
||||
down[i].push(index);
|
||||
previous = index;
|
||||
}
|
||||
}
|
||||
|
||||
heights[x][y] = top + height;
|
||||
indices[x][y] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Brick { up , down }
|
||||
}
|
||||
|
||||
#[aoc(day22, part1)]
|
||||
fn part1(input: &Brick) -> usize {
|
||||
let Brick { down, .. } = input;
|
||||
let mut safe = vec![true; down.len()];
|
||||
|
||||
for underneath in down {
|
||||
if underneath.len() == 1 {
|
||||
safe[underneath[0]] = false;
|
||||
}
|
||||
}
|
||||
|
||||
safe.iter().filter(|&&b| b).count()
|
||||
}
|
||||
|
||||
#[aoc(day22, part2)]
|
||||
fn part2(input: &Brick) -> usize {
|
||||
let Brick { up, down } = input;
|
||||
let mut safe = vec![true; down.len()];
|
||||
|
||||
for underneath in down {
|
||||
if underneath.len() == 1 {
|
||||
safe[underneath[0]] = false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = 0;
|
||||
let mut todo = VecDeque::new();
|
||||
let mut removed = vec![usize::MAX; down.len()];
|
||||
|
||||
for (start, &safe) in safe.iter().enumerate() {
|
||||
if safe {
|
||||
continue;
|
||||
}
|
||||
|
||||
todo.push_back(start);
|
||||
removed[start] = start;
|
||||
|
||||
while let Some(current) = todo.pop_front() {
|
||||
for &next in &up[current] {
|
||||
if removed[next] != start && down[next].iter().all(|&i| removed[i] == start) {
|
||||
result += 1;
|
||||
removed[next] = start;
|
||||
todo.push_back(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const EX: &str = r"1,0,1~1,2,1
|
||||
0,0,2~2,0,2
|
||||
0,2,3~2,2,3
|
||||
0,0,4~0,2,4
|
||||
2,0,5~2,2,5
|
||||
0,1,6~2,1,6
|
||||
1,1,8~1,1,9";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&parse(EX)), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&parse(EX)), 7);
|
||||
}
|
||||
}
|
156
src/day23.rs
Normal file
156
src/day23.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
|
||||
const NEIGHBORS: &[(i64,i64)] = &[(-1,0),(0,1),(1,0),(0,-1)];
|
||||
use itertools::Itertools;
|
||||
|
||||
#[aoc_generator(day23)]
|
||||
fn parse(input: &str) -> Vec<Vec<char>> {
|
||||
input.lines().map(|x| x.chars().collect::<Vec<_>>()).collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn dfs(graph: &HashMap<(usize,usize), Vec<(usize,usize,usize)>>, seen: &mut Vec<Vec<bool>>, (r,c): (usize, usize)) -> Option<usize> {
|
||||
if r == seen.len() - 1 {
|
||||
return Some(0);
|
||||
}
|
||||
let mut max_dist = None;
|
||||
for &(rr, cc, d) in &graph[&(r,c)] {
|
||||
if !seen[rr][cc] {
|
||||
seen[rr][cc] = true;
|
||||
if let Some(dist) = dfs(graph, seen, (rr,cc)) {
|
||||
max_dist = Some(max_dist.unwrap_or(0).max(d+dist))
|
||||
}
|
||||
seen[rr][cc] = false;
|
||||
}
|
||||
}
|
||||
|
||||
max_dist
|
||||
}
|
||||
|
||||
#[aoc(day23, part1)]
|
||||
fn part1(grid: &Vec<Vec<char>>) -> usize {
|
||||
let mut graph = HashMap::<_,Vec<_>>::new();
|
||||
for (r, c) in (0..grid.len()).cartesian_product(0..grid[0].len()) {
|
||||
let neighbors = match grid[r][c] {
|
||||
'#' => continue,
|
||||
'.' => NEIGHBORS,
|
||||
'^' => &NEIGHBORS[0..][..1],
|
||||
'>' => &NEIGHBORS[1..][..1],
|
||||
'v' => &NEIGHBORS[2..][..1],
|
||||
'<' => &NEIGHBORS[3..][..1],
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let e = graph.entry((r,c)).or_default();
|
||||
|
||||
for (dr, dc) in neighbors {
|
||||
let rr = (r as i64 + dr) as usize;
|
||||
let cc = (c as i64 + dc) as usize;
|
||||
if grid.get(rr).and_then(|row| row.get(cc)).is_some_and(|&t| t != '#') {
|
||||
e.push((rr,cc,1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let corridors = graph.iter()
|
||||
.filter(|(_, n)| n.len() == 2)
|
||||
.map(|(&node, _)| node)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (r,c) in corridors {
|
||||
let neighbors = graph.remove(&(r,c)).unwrap();
|
||||
let (r1, c1, d1) = neighbors[0];
|
||||
let (r2, c2, d2) = neighbors[1];
|
||||
let n1 = graph.get_mut(&(r1,c1)).unwrap();
|
||||
if let Some(i) = n1.iter().position(|&(rr,cc,_)| (rr,cc) == (r,c)) {
|
||||
n1[i] = (r2,c2,d1+d2);
|
||||
}
|
||||
let n2 = graph.get_mut(&(r2,c2)).unwrap();
|
||||
if let Some(i) = n2.iter().position(|&(rr,cc,_)| (rr,cc) == (r,c)) {
|
||||
n2[i] = (r1,c1,d1+d2);
|
||||
}
|
||||
}
|
||||
|
||||
dfs(&graph, &mut vec![vec![false; grid[0].len()]; grid.len()], (0,1)).unwrap()
|
||||
}
|
||||
|
||||
#[aoc(day23, part2)]
|
||||
fn part2(grid: &Vec<Vec<char>>) -> usize {
|
||||
let mut graph = HashMap::<_,Vec<_>>::new();
|
||||
for (r, c) in (0..grid.len()).cartesian_product(0..grid[0].len()) {
|
||||
let neighbors = match grid[r][c] {
|
||||
'#' => continue,
|
||||
_ => NEIGHBORS,
|
||||
};
|
||||
let e = graph.entry((r,c)).or_default();
|
||||
|
||||
for (dr, dc) in neighbors {
|
||||
let rr = (r as i64 + dr) as usize;
|
||||
let cc = (c as i64 + dc) as usize;
|
||||
if grid.get(rr).and_then(|row| row.get(cc)).is_some_and(|&t| t != '#') {
|
||||
e.push((rr,cc,1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let corridors = graph.iter()
|
||||
.filter(|(_, n)| n.len() == 2)
|
||||
.map(|(&node, _)| node)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (r,c) in corridors {
|
||||
let neighbors = graph.remove(&(r,c)).unwrap();
|
||||
let (r1, c1, d1) = neighbors[0];
|
||||
let (r2, c2, d2) = neighbors[1];
|
||||
let n1 = graph.get_mut(&(r1,c1)).unwrap();
|
||||
if let Some(i) = n1.iter().position(|&(rr,cc,_)| (rr,cc) == (r,c)) {
|
||||
n1[i] = (r2,c2,d1+d2);
|
||||
}
|
||||
let n2 = graph.get_mut(&(r2,c2)).unwrap();
|
||||
if let Some(i) = n2.iter().position(|&(rr,cc,_)| (rr,cc) == (r,c)) {
|
||||
n2[i] = (r1,c1,d1+d2);
|
||||
}
|
||||
}
|
||||
|
||||
dfs(&graph, &mut vec![vec![false; grid[0].len()]; grid.len()], (0,1)).unwrap()
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const EX: &str = r"#.#####################
|
||||
#.......#########...###
|
||||
#######.#########.#.###
|
||||
###.....#.>.>.###.#.###
|
||||
###v#####.#v#.###.#.###
|
||||
###.>...#.#.#.....#...#
|
||||
###v###.#.#.#########.#
|
||||
###...#.#.#.......#...#
|
||||
#####.#.#.#######.#.###
|
||||
#.....#.#.#.......#...#
|
||||
#.#####.#.#.#########v#
|
||||
#.#...#...#...###...>.#
|
||||
#.#.#v#######v###.###v#
|
||||
#...#.>.#...>.>.#.###.#
|
||||
#####v#.#.###v#.#.###.#
|
||||
#.....#...#...#.#.#...#
|
||||
#.#########.###.#.#.###
|
||||
#...###...#...#...#.###
|
||||
###.###.#.###v#####v###
|
||||
#...#...#.#.>.>.#.>.###
|
||||
#.###.###.#.###.#.#v###
|
||||
#.....###...###...#...#
|
||||
#####################.#";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&parse(EX)), 94);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&parse(EX)), 154);
|
||||
}
|
||||
}
|
144
src/day24.rs
Normal file
144
src/day24.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
|
||||
//const RANGE: RangeInclusive<f64> = 7.0..=27.0;
|
||||
const RANGE: RangeInclusive<f64> = 200_000_000_000_000.0..=400_000_000_000_000.0;
|
||||
|
||||
#[aoc_generator(day24)]
|
||||
fn parse(input: &str) -> Vec<[f64; 6]> {
|
||||
let input: Vec<[f64; 6]> = input.lines()
|
||||
.map(|line| {
|
||||
line.split("@")
|
||||
.flat_map(|components| {
|
||||
components.split(",")
|
||||
.map(str::trim)
|
||||
.map(|num| num.parse::<f64>().unwrap())
|
||||
})
|
||||
.collect::<Vec<_>>().try_into().unwrap()
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
input
|
||||
}
|
||||
|
||||
#[aoc(day24, part1)]
|
||||
fn part1(input: &[[f64; 6]]) -> u32 {
|
||||
let mut total = 0;
|
||||
|
||||
// It's just solve the linear equations. I'm using a matrix.
|
||||
for i in 1..input.len() {
|
||||
for j in 0..i {
|
||||
let [a, b, _, d, e, _] = input[i];
|
||||
let [g, h, _, j, k, _] = input[j];
|
||||
|
||||
let determinant = e * j - d * k;
|
||||
if determinant == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let t = (j * (h - b) - k * (g - a)) / determinant;
|
||||
let u = (d * (h - b) - e * (g - a)) / determinant;
|
||||
|
||||
let x = a + t * d;
|
||||
let y = b + t * e;
|
||||
|
||||
if t >= 0.0 && u >= 0.0 && RANGE.contains(&x) && RANGE.contains(&y) {
|
||||
total += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
#[aoc(day24, part2)]
|
||||
fn part2(input: &[[f64; 6]]) -> f64 {
|
||||
let [a, b, c, d, e, f] = input[0];
|
||||
let [g, h, i, j, k, l] = input[1];
|
||||
let [m, n, o, p, q, r] = input[2];
|
||||
|
||||
let mut matrix = [
|
||||
[0.0, l - f, e - k, 0.0, c - i, h - b, e * c - b * f + h * l - k * i],
|
||||
[0.0, r - f, e - q, 0.0, c - o, n - b, e * c - b * f + n * r - q * o],
|
||||
[f - l, 0.0, j - d, i - c, 0.0, a - g, a * f - d * c + j * i - g * l],
|
||||
[f - r, 0.0, p - d, o - c, 0.0, a - m, a * f - d * c + p * o - m * r],
|
||||
[k - e, d - j, 0.0, b - h, g - a, 0.0, d * b - a * e + g * k - j * h],
|
||||
[q - e, d - p, 0.0, b - n, m - a, 0.0, d * b - a * e + m * q - p * n],
|
||||
];
|
||||
|
||||
// Use Gaussian elimination to solve for the 6 unknowns.
|
||||
// Forward elimination, processing columns from left to right.
|
||||
// This will leave a matrix in row echelon form.
|
||||
// That's right, I got a D in applied linear algebra.
|
||||
for pivot in 0..6 {
|
||||
// Make leading coefficient of each row positive to make subsequent calculations easier.
|
||||
for row in &mut matrix[pivot..] {
|
||||
if row[pivot] < 0.0 {
|
||||
row.iter_mut().for_each(|num| *num = -*num);
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
let column = matrix.map(|row| row[pivot]);
|
||||
|
||||
// If only one non-zero coefficient remaining in the column then we're done.
|
||||
if column[pivot..].iter().filter(|&&c| c > 0.0).count() == 1 {
|
||||
// Move this row into the pivot location
|
||||
let index = column.iter().rposition(|&c| c > 0.0).unwrap();
|
||||
matrix.swap(pivot, index);
|
||||
break;
|
||||
}
|
||||
|
||||
// Find the row with the lowest non-zero leading coefficient.
|
||||
let min = *column[pivot..].iter().filter(|&&c| c > 0.0).min_by(|a, b| a.total_cmp(b)).unwrap();
|
||||
let index = column.iter().rposition(|&c| c == min).unwrap();
|
||||
|
||||
// Subtract as many multiples of this minimum row from each other row as possible
|
||||
// to shrink the coefficients of our column towards zero.
|
||||
for row in pivot..6 {
|
||||
if row != index && column[row] != 0.0 {
|
||||
let factor = column[row] / min;
|
||||
|
||||
for col in pivot..7 {
|
||||
matrix[row][col] -= factor * matrix[index][col];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Back substitution, processing columns from right to left.
|
||||
// This will leave the matrix in reduced row echelon form.
|
||||
// The solved unknowns are then in the 7th column.
|
||||
for pivot in (0..6).rev() {
|
||||
matrix[pivot][6] /= matrix[pivot][pivot];
|
||||
|
||||
for row in 0..pivot {
|
||||
matrix[row][6] -= matrix[pivot][6] * matrix[row][pivot];
|
||||
}
|
||||
}
|
||||
|
||||
(matrix[0][6] + matrix[1][6] + matrix[2][6]).floor()
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const EX: &str = r"19, 13, 30 @ -2, 1, -2
|
||||
18, 19, 22 @ -1, -1, -2
|
||||
20, 25, 34 @ -2, -2, -4
|
||||
12, 31, 28 @ -1, -2, -1
|
||||
20, 19, 15 @ 1, -5, -3";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&parse(EX)), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part2_example() {
|
||||
assert_eq!(part2(&parse(EX)), 47.0);
|
||||
}
|
||||
}
|
109
src/day25.rs
Normal file
109
src/day25.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use std::collections::{HashSet, HashMap};
|
||||
use rand::Rng;
|
||||
|
||||
use aoc_runner_derive::{aoc, aoc_generator};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Graph {
|
||||
edges: Vec<(String, String)>,
|
||||
vertex_count: usize
|
||||
}
|
||||
|
||||
impl Graph {
|
||||
fn cut(&mut self) -> (usize, usize, usize) {
|
||||
let mut contracted_edges = self.edges.clone();
|
||||
let mut contracted_vertex_count = self.vertex_count;
|
||||
let mut contracted: HashMap<String, Vec<String>> = HashMap::new();
|
||||
|
||||
while contracted_vertex_count > 2 {
|
||||
let random = rand::thread_rng().gen_range(0..contracted_edges.len());
|
||||
let edge_to_contract = contracted_edges[random].clone();
|
||||
if contracted.contains_key(&edge_to_contract.0) {
|
||||
contracted.get_mut(&edge_to_contract.0.clone()).unwrap().push(edge_to_contract.1.clone());
|
||||
} else {
|
||||
contracted.insert(edge_to_contract.0.clone(), vec![edge_to_contract.1.clone()]);
|
||||
}
|
||||
|
||||
if contracted.contains_key(&edge_to_contract.1) {
|
||||
let mut to_append = contracted.clone().get_mut(&edge_to_contract.1).unwrap().clone();
|
||||
contracted.get_mut(&edge_to_contract.0).unwrap().append(&mut to_append);
|
||||
contracted.remove(&edge_to_contract.1);
|
||||
}
|
||||
|
||||
let mut new_edges: Vec<(String, String)> = vec![];
|
||||
|
||||
for edge in contracted_edges {
|
||||
if edge.1 == edge_to_contract.1 {
|
||||
new_edges.push((edge.0.clone(), edge_to_contract.0.clone()));
|
||||
} else if edge.0 == edge_to_contract.1 {
|
||||
new_edges.push((edge_to_contract.0.clone(), edge.1.clone()));
|
||||
} else {
|
||||
new_edges.push(edge.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let tmp = new_edges.iter().cloned().filter(|x| x.0 != x.1).collect::<Vec<_>>();
|
||||
|
||||
contracted_edges = tmp;
|
||||
contracted_vertex_count -= 1;
|
||||
|
||||
}
|
||||
|
||||
let counts = contracted.iter().map(|x| x.1.len() + 1).collect::<Vec<_>>();
|
||||
|
||||
return (contracted_edges.len(), counts[0], *counts.last().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[aoc_generator(day25)]
|
||||
fn parse(input: &str) -> Graph {
|
||||
let input = input.lines().flat_map(|line| {
|
||||
let line = line.split(' ').map(|x| x.trim_end_matches(':').to_string()).collect::<Vec<_>>();
|
||||
line[1..].iter().cloned().map(|x| (line[0].clone(), x)).collect::<Vec<_>>()
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
let vertex_count = input.clone().into_iter().flat_map(|x| vec![x.0, x.1]).collect::<HashSet<_>>().len();
|
||||
Graph {
|
||||
edges: input,
|
||||
vertex_count
|
||||
}
|
||||
}
|
||||
|
||||
#[aoc(day25, part1)]
|
||||
fn part1(graph: &Graph) -> usize {
|
||||
let graph = &mut graph.clone();
|
||||
let mut min_cut = usize::MAX;
|
||||
let mut count1 = 0;
|
||||
let mut count2 = 0;
|
||||
|
||||
while min_cut != 3 {
|
||||
(min_cut, count1, count2) = graph.cut();
|
||||
}
|
||||
|
||||
count1 * count2
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const EX: &str = r"jqt: rhn xhk nvd
|
||||
rsh: frs pzl lsr
|
||||
xhk: hfx
|
||||
cmg: qnr nvd lhk bvb
|
||||
rhn: xhk bvb hfx
|
||||
bvb: xhk hfx
|
||||
pzl: lsr hfx nvd
|
||||
qnr: nvd
|
||||
ntq: jqt hfx bvb xhk
|
||||
nvd: lhk
|
||||
lsr: lhk
|
||||
rzs: qnr cmg lsr rsh
|
||||
frs: qnr lhk lsr";
|
||||
|
||||
#[test]
|
||||
fn part1_example() {
|
||||
assert_eq!(part1(&parse(EX)), 54);
|
||||
}
|
||||
}
|
@@ -1,3 +1,10 @@
|
||||
mod day25;
|
||||
mod day24;
|
||||
mod day23;
|
||||
mod day22;
|
||||
mod day21;
|
||||
mod day20;
|
||||
mod day19;
|
||||
mod day18;
|
||||
mod day17;
|
||||
mod day16;
|
||||
|
Reference in New Issue
Block a user