Skip to content

Commit f9c3c7a

Browse files
committed
Year 2023 Day 12 - Reverse DFA
1 parent 1e9a91e commit f9c3c7a

File tree

4 files changed

+301
-183
lines changed

4 files changed

+301
-183
lines changed

y23/d12-hot-springs/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,3 @@ edition = "2021"
77

88
[dependencies]
99
aoc = { path = "../../aoc" }
10-
itertools = "0.12.0"
11-
rayon = "1.8.0"

y23/d12-hot-springs/src/main.rs

Lines changed: 161 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,206 +1,187 @@
1-
use aoc::input_str;
2-
use itertools::Itertools;
3-
use rayon::prelude::*;
4-
5-
#[derive(Debug, PartialEq, Clone, Copy)]
6-
enum Cell {
7-
Empty,
8-
Filled,
9-
Unknown,
1+
// Builds some kind of DFA
2+
//
3+
// In the example '?###???????? 3,2,1' the generate machine is
4+
//
5+
// \.* - beginning (repeat . && accept #)
6+
// ### - 3! (accept # , accept # , accept .)
7+
// \.+ - spacing (repeat . && accept #)
8+
// ## - 2! (accept # , accept .)
9+
// \.+ - spacing (repeat . && accept #)
10+
// # - 1! (accept .)
11+
// \.* - ending (repeat .)
12+
//
13+
// Once the 'machine' is built running through the particular input requires keeping track of how heads on are each particular state and successfully book keeping when the next token is read.
14+
// question marks ? split individual DFA states into 2
15+
16+
use aoc::{input_str, time};
17+
18+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19+
enum Condition {
20+
Good, // .
21+
Bad, // #
22+
Unknown, // ?
1023
}
1124

12-
impl Cell {
13-
fn from_char(c: char) -> Self {
14-
match c {
15-
'.' => Self::Empty,
16-
'#' => Self::Filled,
17-
'?' => Self::Unknown,
18-
_ => panic!("Invalid character"),
19-
}
20-
}
25+
#[derive(Debug, Clone, Copy)]
26+
enum States {
27+
Spacing, // repeat . && accept #
28+
AcceptDot, // accept .
29+
AcceptTag, // accept #
30+
Ending, // repeat .
2131
}
2232

23-
#[derive(Debug, PartialEq)]
24-
struct Row {
25-
cells: Vec<Cell>,
26-
clues: Vec<usize>,
33+
fn parse_line(line: &str) -> (Vec<Condition>, Vec<usize>) {
34+
let mut split = line.split(' ');
35+
36+
let conditions = split
37+
.next()
38+
.unwrap()
39+
.chars()
40+
.map(|c| match c {
41+
'.' => Condition::Good,
42+
'#' => Condition::Bad,
43+
'?' => Condition::Unknown,
44+
_ => unreachable!(),
45+
})
46+
.collect();
47+
48+
let groups = split
49+
.next()
50+
.unwrap()
51+
.split(',')
52+
.map(|s| s.parse::<usize>().unwrap())
53+
.collect();
54+
55+
(conditions, groups)
2756
}
2857

29-
impl Row {
30-
fn from_str(s: &str) -> Self {
31-
// split at spaces
32-
let mut groups = s.split_whitespace();
33-
34-
// parse cells
35-
let cells = groups
36-
.next()
37-
.unwrap()
38-
.chars()
39-
.map(Cell::from_char)
40-
.collect();
41-
42-
// parse clues
43-
let clues = groups
44-
.next()
45-
.unwrap()
46-
.split(',')
47-
.map(|s| s.parse().unwrap())
48-
.collect();
49-
50-
Self { cells, clues }
51-
}
52-
53-
fn duplicate(&mut self) {
54-
self.cells = self
55-
.cells
56-
.iter()
57-
.cycle()
58-
.take(self.cells.len() * 5)
59-
.cloned()
60-
.collect();
61-
62-
self.clues = self
63-
.clues
64-
.iter()
65-
.cycle()
66-
.take(self.clues.len() * 5)
67-
.cloned()
68-
.collect();
69-
}
70-
71-
// Checks if a set of spaces works for this row.
72-
fn check(&self, spaces: &[usize]) -> bool {
73-
let mut spaces = spaces.iter();
74-
let clues = self.clues.iter();
75-
76-
// Run the first space manually.
77-
let mut index = spaces.next().cloned().unwrap();
78-
if self.cells[0..index].iter().any(|c| *c == Cell::Filled) {
79-
return false;
58+
fn process(gears: &[Condition], groups: &[usize]) -> usize {
59+
// The dfa has 2 states for the start and end with \.* patterns
60+
// Each group G gets g = |G| states for the line of # {g}
61+
// Between each group 1 state in inserted for the \.+ pattern
62+
63+
let mut states: Vec<States> = groups
64+
.iter()
65+
.flat_map(|g| {
66+
std::iter::once(States::Spacing)
67+
.chain(std::iter::repeat(States::AcceptTag).take(g - 1))
68+
.chain(std::iter::once(States::AcceptDot))
69+
})
70+
.collect();
71+
states.push(States::Ending);
72+
73+
let mut counts = vec![0_usize; states.len() + 1];
74+
let mut next = counts.clone();
75+
76+
// Start with 1 DFA in the starting state
77+
counts[0] = 1;
78+
79+
for cond in gears {
80+
for (idx, (state, count)) in states.iter().zip(counts.iter()).enumerate() {
81+
match (state, cond) {
82+
(States::Spacing, Condition::Good) => next[idx] += count,
83+
(States::Spacing, Condition::Bad) => next[idx + 1] += count,
84+
(States::Spacing, Condition::Unknown) => {
85+
next[idx] += count;
86+
next[idx + 1] += count;
87+
}
88+
(States::AcceptDot, Condition::Good) => next[idx + 1] += count,
89+
(States::AcceptDot, Condition::Bad) => {}
90+
(States::AcceptDot, Condition::Unknown) => next[idx + 1] += count,
91+
(States::AcceptTag, Condition::Good) => {}
92+
(States::AcceptTag, Condition::Bad) => next[idx + 1] += count,
93+
(States::AcceptTag, Condition::Unknown) => next[idx + 1] += count,
94+
(States::Ending, Condition::Good) => next[idx] += count,
95+
(States::Ending, Condition::Bad) => {}
96+
(States::Ending, Condition::Unknown) => next[idx] += count,
97+
}
8098
}
8199

82-
// Loop over the rest of pairs of spaces and clues.
83-
debug_assert_eq!(spaces.len(), clues.len());
100+
counts = vec![0; states.len()];
101+
std::mem::swap(&mut counts, &mut next);
102+
}
84103

85-
for (&clue, &space) in clues.zip(spaces) {
86-
// Check that the clues are filled.
87-
if self.cells[index..index + clue]
88-
.iter()
89-
.any(|c| *c == Cell::Empty)
90-
{
91-
return false;
92-
}
104+
counts[counts.len() - 2] + counts[counts.len() - 1]
105+
}
93106

94-
index += clue;
107+
fn part1(input: &str) -> usize {
108+
input
109+
.lines()
110+
.map(parse_line)
111+
.map(|(gears, groups)| process(&gears, &groups))
112+
.sum()
113+
}
95114

96-
// Check that the spaces are empty.
97-
if self.cells[index..index + space]
98-
.iter()
99-
.any(|c| *c == Cell::Filled)
100-
{
101-
return false;
115+
fn part2(input: &str) -> usize {
116+
input
117+
.lines()
118+
.map(parse_line)
119+
.map(|(gears, groups)| {
120+
let mut joined_gears = gears.clone();
121+
for _ in 0..4 {
122+
joined_gears.push(Condition::Unknown);
123+
joined_gears.extend(gears.iter().cloned());
102124
}
103-
104-
index += space;
105-
}
106-
107-
// If all tests passed, then this is a valid solution.
108-
true
109-
}
110-
111-
// Part 1, find number of possible solutions.
112-
fn part1(&self) -> usize {
113-
// empty squares + filled squares = total squares
114-
//
115-
// or we can rearrange to:
116-
//
117-
// empty squares = total squares - filled squares
118-
let empty_squares = self.cells.len() - self.clues.iter().sum::<usize>();
119-
120-
// We start by giving each clue a single empty square to its sides.
121-
let mut spaces = vec![0; self.clues.len() + 1];
122-
// The middle spaces are at least length 1
123-
let range = 1..=self.clues.len() - 1;
124-
spaces[range].iter_mut().for_each(|s| *s = 1);
125-
126-
// In total we have empty_squares - self.clues.len() spaces to
127-
// distribute among all the spaces.
128-
129-
let left_over = empty_squares - (self.clues.len() - 1);
130-
131-
let set = (0..spaces.len()).collect_vec();
132-
set.into_iter()
133-
.combinations_with_replacement(left_over)
134-
.par_bridge()
135-
.map(|s| {
136-
let mut spaces = spaces.clone();
137-
for i in s.iter() {
138-
spaces[*i] += 1;
139-
}
140-
spaces
141-
})
142-
.filter(|s| {
143-
// Check that the spaces work.
144-
self.check(s)
145-
})
146-
.count()
147-
}
125+
(joined_gears, groups.repeat(5))
126+
})
127+
.map(|(gears, groups)| process(&gears, &groups))
128+
.sum()
148129
}
149130

150131
fn main() {
151132
let input = input_str!(2023, 12);
152-
let rows = input.lines().map(Row::from_str).collect::<Vec<_>>();
153-
154-
println!("Part 1: {}", rows.iter().map(Row::part1).sum::<usize>());
155-
156-
// Part 2: Duplicated cells and clues by 5
157-
println!(
158-
"Part 2: {}",
159-
rows.into_par_iter()
160-
.enumerate()
161-
.map(|(i, mut row)| {
162-
row.duplicate();
163-
let result = row.part1();
164-
println!("{}: {}", i, result);
165-
result
166-
})
167-
.sum::<usize>()
168-
);
133+
let part1 = time("Part 1", || part1(input));
134+
println!("Part 1: {}", part1);
135+
let part2 = time("Part 2", || part2(input));
136+
println!("Part 2: {}", part2);
169137
}
170138

171139
#[cfg(test)]
172-
mod test {
173-
use super::*;
140+
mod tests {
141+
use crate::*;
142+
143+
#[test]
144+
fn test_parsing() {
145+
let input = "???.### 1,1,3";
146+
let conditions = vec![
147+
Condition::Unknown,
148+
Condition::Unknown,
149+
Condition::Unknown,
150+
Condition::Good,
151+
Condition::Bad,
152+
Condition::Bad,
153+
Condition::Bad,
154+
];
155+
let gears: Vec<usize> = vec![1, 1, 3];
156+
assert_eq!((conditions, gears), parse_line(input));
157+
}
158+
159+
#[test]
160+
fn examples() {
161+
let tests = [
162+
("???.### 1,1,3", 1, 1),
163+
(".??..??...?##. 1,1,3", 4, 16384),
164+
("?#?#?#?#?#?#?#? 1,3,1,6", 1, 1),
165+
("????.#...#... 4,1,1", 1, 16),
166+
("????.######..#####. 1,6,5", 4, 2500),
167+
("?###???????? 3,2,1", 10, 506250),
168+
];
169+
170+
for (input, p1, p2) in tests {
171+
assert_eq!(part1(input), p1, "{}", input);
172+
assert_eq!(part2(input), p2, "{}", input);
173+
}
174+
}
174175

175176
#[test]
176-
fn test_column() {
177-
// ???.### 1,1,3
178-
let row = Row::from_str("???.### 1,1,3");
179-
assert_eq!(
180-
row.cells,
181-
vec![
182-
Cell::Unknown,
183-
Cell::Unknown,
184-
Cell::Unknown,
185-
Cell::Empty,
186-
Cell::Filled,
187-
Cell::Filled,
188-
Cell::Filled
189-
]
190-
);
191-
assert_eq!(row.clues, vec![1, 1, 3]);
177+
fn answer_part1() {
178+
let input = input_str!(2023, 12);
179+
assert_eq!(part1(input), 7402);
192180
}
193181

194182
#[test]
195-
fn test_example() {
196-
let input = "???.### 1,1,3\n.??..??...?##. 1,1,3\n?#?#?#?#?#?#?#? 1,3,1,6\n????.#...#... 4,1,1\n????.######..#####. 1,6,5\n?###???????? 3,2,1";
197-
let rows = input.lines().map(Row::from_str).collect::<Vec<_>>();
198-
199-
assert_eq!(rows[0].part1(), 1);
200-
assert_eq!(rows[1].part1(), 4);
201-
assert_eq!(rows[2].part1(), 1);
202-
assert_eq!(rows[3].part1(), 1);
203-
assert_eq!(rows[4].part1(), 4);
204-
assert_eq!(rows[5].part1(), 10);
183+
fn answer_part2() {
184+
let input = input_str!(2023, 12);
185+
assert_eq!(part2(input), 3384337640277);
205186
}
206187
}

y23/d17/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ edition = "2021"
77

88
[dependencies]
99
aoc = { path = "../../aoc" }
10+
smallvec = "1.11.2"

0 commit comments

Comments
 (0)