Skip to content

Commit 8119c89

Browse files
committed
Day 19
1 parent 98a9032 commit 8119c89

File tree

3 files changed

+193
-2
lines changed

3 files changed

+193
-2
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
2828
| [Day 15](./src/bin/15.rs) | `119.7µs` | `591.0µs` |
2929
| [Day 16](./src/bin/16.rs) | `158.9µs` | `202.6µs` |
3030
| [Day 17](./src/bin/17.rs) | `1.7µs` | `361.3µs` |
31-
| [Day 18](./src/bin/18.rs) | `47.7µs` | `56.9µs` |
31+
| [Day 18](./src/bin/18.rs) | `49.6µs` | `58.9µs` |
32+
| [Day 19](./src/bin/19.rs) | `204.0µs` | `693.5µs` |
3233

33-
**Total: 13.56ms**
34+
**Total: 14.46ms**
3435
<!--- benchmarking table --->
3536

3637
---

data/examples/19.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
r, wr, b, g, bwu, rb, gb, br
2+
3+
brwrr
4+
bggr
5+
gbbr
6+
rrbgbr
7+
ubwu
8+
bwurrg
9+
brgr
10+
bbrgwb

src/bin/19.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use std::collections::HashMap;
2+
3+
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
4+
5+
advent_of_code::solution!(19);
6+
7+
#[inline]
8+
fn char_to_idx(c: char) -> usize {
9+
(c as usize - 'a' as usize) as usize
10+
}
11+
12+
#[derive(Clone, Debug)]
13+
struct TrieTree {
14+
next: [Option<Box<TrieTree>>; 26],
15+
max_len: usize,
16+
is_end: bool,
17+
}
18+
19+
impl TrieTree {
20+
fn new() -> Self {
21+
Self {
22+
next: core::array::from_fn(|_| None),
23+
max_len: 0,
24+
is_end: false,
25+
}
26+
}
27+
28+
fn from_words(words: &[&str]) -> Self {
29+
let mut tree = Self::new();
30+
31+
let mut max_len = 0;
32+
for word in words {
33+
tree.insert(*word);
34+
max_len = max_len.max(word.len());
35+
}
36+
37+
assert_eq!(tree.is_end, false);
38+
tree.max_len = max_len;
39+
tree
40+
}
41+
42+
fn insert(&mut self, word: &str) {
43+
let mut node = self;
44+
45+
for c in word.chars() {
46+
let index = char_to_idx(c);
47+
match node.next[index] {
48+
Some(ref mut child) => node = child,
49+
None => {
50+
node.next[index] = Some(Box::new(TrieTree::new()));
51+
node = node.next[index].as_mut().unwrap();
52+
}
53+
}
54+
}
55+
56+
node.is_end = true;
57+
node.max_len = node.max_len.max(word.len());
58+
}
59+
60+
#[inline]
61+
fn get_all_prefixes<'a>(&self, design: &'a str) -> Vec<&'a str> {
62+
let mut prefixes = Vec::with_capacity(10);
63+
let design_chars = design.chars().collect::<Vec<_>>();
64+
65+
let mut q = Vec::new();
66+
q.push((self, 0));
67+
68+
while let Some((node, design_idx)) = q.pop() {
69+
if node.is_end {
70+
prefixes.push(&design[..design_idx]);
71+
}
72+
if design_idx == design.len() {
73+
continue;
74+
}
75+
76+
let index = char_to_idx(design_chars[design_idx]);
77+
if let Some(boxed_child) = &node.next[index] {
78+
q.push((boxed_child.as_ref(), design_idx + 1));
79+
}
80+
}
81+
82+
prefixes
83+
}
84+
}
85+
86+
fn is_valid_design(trie: &TrieTree, design: &str) -> bool {
87+
let mut q = Vec::new();
88+
q.push((trie, 0));
89+
90+
let design_chars = design.chars().collect::<Vec<_>>();
91+
92+
while let Some((node, design_idx)) = q.pop() {
93+
if design_idx == design.len() {
94+
match node.is_end {
95+
true => return true,
96+
false => continue,
97+
}
98+
}
99+
100+
if node.is_end {
101+
q.push((trie, design_idx));
102+
}
103+
104+
let idx = char_to_idx(design_chars[design_idx]);
105+
if let Some(boxed_child) = &node.next[idx] {
106+
q.push((boxed_child.as_ref(), design_idx + 1));
107+
}
108+
}
109+
110+
false
111+
}
112+
113+
fn count_valid_designs<'a>(
114+
cached_counts: &mut HashMap<&'a str, u64>,
115+
trie: &TrieTree,
116+
design: &'a str,
117+
) -> u64 {
118+
if cached_counts.contains_key(design) {
119+
return *cached_counts.get(design).unwrap();
120+
}
121+
122+
let mut count = 0;
123+
for prefix in trie.get_all_prefixes(design) {
124+
// dbg!(&design, &prefix);
125+
if prefix.len() == design.len() {
126+
count += 1;
127+
}
128+
count += count_valid_designs(cached_counts, trie, &design[prefix.len()..]);
129+
}
130+
131+
// dbg!(&design, count);
132+
cached_counts.insert(design, count);
133+
count
134+
}
135+
136+
fn parse_input(input: &str) -> (TrieTree, Vec<&str>) {
137+
let (towels, designs) = input.split_once("\n\n").unwrap();
138+
let towels = towels.split(", ").collect::<Vec<_>>();
139+
let designs = designs.lines().collect::<Vec<_>>();
140+
(TrieTree::from_words(&towels), designs)
141+
}
142+
143+
pub fn part_one(input: &str) -> Option<u64> {
144+
let (trie, designs) = parse_input(input);
145+
146+
let valid_count = designs
147+
.iter()
148+
.filter(|&design| is_valid_design(&trie, design))
149+
.count();
150+
151+
Some(valid_count as u64)
152+
}
153+
154+
pub fn part_two(input: &str) -> Option<u64> {
155+
let (trie, designs) = parse_input(input);
156+
157+
let valid_count = designs
158+
.par_iter()
159+
.map(|design| count_valid_designs(&mut HashMap::new(), &trie, design))
160+
.sum();
161+
162+
Some(valid_count)
163+
}
164+
165+
#[cfg(test)]
166+
mod tests {
167+
use super::*;
168+
169+
#[test]
170+
fn test_part_one() {
171+
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
172+
assert_eq!(result, Some(6));
173+
}
174+
175+
#[test]
176+
fn test_part_two() {
177+
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
178+
assert_eq!(result, Some(16));
179+
}
180+
}

0 commit comments

Comments
 (0)