Skip to content

Commit d7031ea

Browse files
2024: Optimize day 19
1 parent 10511b4 commit d7031ea

File tree

2 files changed

+123
-6
lines changed

2 files changed

+123
-6
lines changed

2024/day19/src/main.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
use std::collections::HashMap;
22
use std::fs;
33

4-
fn dfs<'a>(design: &'a str, patterns: &[&str], cache: &mut HashMap<&'a str, usize>) -> usize {
4+
use trie::Trie;
5+
6+
mod trie;
7+
8+
fn dfs<'a>(design: &'a str, patterns: &Trie, cache: &mut HashMap<&'a str, usize>) -> usize {
59
if design.is_empty() {
610
return 1;
711
}
@@ -11,10 +15,8 @@ fn dfs<'a>(design: &'a str, patterns: &[&str], cache: &mut HashMap<&'a str, usiz
1115
}
1216

1317
let mut r = 0;
14-
for t in patterns {
15-
if let Some(rest) = design.strip_prefix(t) {
16-
r += dfs(rest, patterns, cache);
17-
}
18+
for l in patterns.common_prefix_lengths(design) {
19+
r += dfs(&design[l..], patterns, cache);
1820
}
1921

2022
cache.insert(design, r);
@@ -28,11 +30,18 @@ fn main() {
2830
let patterns = lines[0].split(", ").collect::<Vec<_>>();
2931
let designs = &lines[2..];
3032

33+
// create index structure that allows us to quickly search for common
34+
// prefix lengths
35+
let mut trie = Trie::default();
36+
for p in &patterns {
37+
trie.insert(p);
38+
}
39+
3140
let mut seen = HashMap::new();
3241
let mut total1 = 0;
3342
let mut total2 = 0;
3443
for d in designs {
35-
let c = dfs(d, &patterns, &mut seen);
44+
let c = dfs(d, &trie, &mut seen);
3645
if c > 0 {
3746
total1 += 1;
3847
total2 += c;

2024/day19/src/trie.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//! A prefix tree (trie) that allows us to quickly search for common prefixes.
2+
//!
3+
//! Since the puzzle input only consists of lowercase characters from 'a' to
4+
//! 'z' and the towel patterns are on average rather short, I didn't bother
5+
//! implementing something sophisticated. A naive implementation where the
6+
//! nodes have an array of 26 pointers to their children is enough and does not
7+
//! consume too much memory.
8+
9+
#[derive(Debug, Default)]
10+
struct Node {
11+
children: [usize; 26],
12+
end: bool,
13+
}
14+
15+
pub struct Trie {
16+
nodes: Vec<Node>,
17+
}
18+
19+
impl Trie {
20+
pub fn default() -> Self {
21+
// insert root
22+
Trie {
23+
nodes: vec![Node::default()],
24+
}
25+
}
26+
27+
/// Insert a prefix into the tree
28+
pub fn insert(&mut self, s: &str) {
29+
let mut current_node = 0;
30+
31+
for c in s.chars() {
32+
debug_assert!(
33+
c.is_ascii_lowercase(),
34+
"This trie implementation can only ASCII lowercase characters"
35+
);
36+
37+
let i = (c as u8 - b'a') as usize;
38+
if self.nodes[current_node].children[i] == 0 {
39+
self.nodes[current_node].children[i] = self.nodes.len();
40+
self.nodes.push(Node::default());
41+
}
42+
43+
current_node = self.nodes[current_node].children[i];
44+
}
45+
46+
self.nodes[current_node].end = true;
47+
}
48+
49+
/// Look for prefixes of the given string and return their lengths. If
50+
/// there is no prefix, an empty Vec will be returned.
51+
pub fn common_prefix_lengths(&self, s: &str) -> Vec<usize> {
52+
let mut result = Vec::new();
53+
let mut current_node = 0;
54+
55+
for (len, c) in s.chars().enumerate() {
56+
debug_assert!(
57+
c.is_ascii_lowercase(),
58+
"This trie implementation can only ASCII lowercase characters"
59+
);
60+
61+
if self.nodes[current_node].end {
62+
result.push(len);
63+
}
64+
65+
let i = (c as u8 - b'a') as usize;
66+
if self.nodes[current_node].children[i] == 0 {
67+
// nothing else to find
68+
return result;
69+
}
70+
71+
current_node = self.nodes[current_node].children[i];
72+
}
73+
74+
if self.nodes[current_node].end {
75+
result.push(s.len());
76+
}
77+
78+
result
79+
}
80+
}
81+
82+
#[cfg(test)]
83+
mod tests {
84+
use super::Trie;
85+
86+
#[test]
87+
fn common_prefix_lengths() {
88+
let mut t = Trie::default();
89+
t.insert("foo");
90+
t.insert("foobar");
91+
t.insert("bar");
92+
t.insert("bra");
93+
t.insert("foobarfoo");
94+
t.insert("fool");
95+
t.insert("foofoo");
96+
t.insert("foono");
97+
t.insert("other");
98+
99+
assert_eq!(t.common_prefix_lengths("fo"), vec![]);
100+
assert_eq!(t.common_prefix_lengths("foo"), vec![3]);
101+
assert_eq!(t.common_prefix_lengths("fool"), vec![3, 4]);
102+
assert_eq!(t.common_prefix_lengths("foofoo"), vec![3, 6]);
103+
assert_eq!(t.common_prefix_lengths("foobar"), vec![3, 6]);
104+
assert_eq!(t.common_prefix_lengths("foobarbar"), vec![3, 6]);
105+
assert_eq!(t.common_prefix_lengths("foobarfoo"), vec![3, 6, 9]);
106+
assert_eq!(t.common_prefix_lengths("foobarfool"), vec![3, 6, 9]);
107+
}
108+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy