Skip to content

Commit 5b83200

Browse files
committed
Day 06
1 parent e33cebb commit 5b83200

File tree

3 files changed

+204
-1
lines changed

3 files changed

+204
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ By default, `cargo time` does not write to the readme. In order to do so, append
217217
| [Day 3](./src/bin/03.rs) | `34.8µs` | `58.8µs` |
218218
| [Day 4](./src/bin/04.rs) | `123.5µs` | `4.0ms` |
219219
| [Day 5](./src/bin/05.rs) | `90.8µs` | `28.2µs` |
220+
| [Day 6](./src/bin/06.rs) | `146.8µs` | `69.3µs` |
220221

221-
**Total: 149.23ms**
222+
**Total: 149.44ms**
222223

223224
<!--- benchmarking table --->

data/examples/06.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
123 328 51 64
2+
45 64 387 23
3+
6 98 215 314
4+
* + * +

src/bin/06.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
advent_of_code::solution!(6);
2+
3+
pub fn part_one(input: &str) -> Option<u64> {
4+
let problems = parse_problems(input)?;
5+
let total: u64 = problems.iter().map(solve_problem).sum();
6+
Some(total)
7+
}
8+
9+
pub fn part_two(input: &str) -> Option<u64> {
10+
let problems = parse_problems_rtl(input)?;
11+
let total: u64 = problems.iter().map(solve_problem).sum();
12+
Some(total)
13+
}
14+
15+
fn parse_problems(input: &str) -> Option<Vec<Problem>> {
16+
let lines: Vec<&str> = input.lines().filter(|line| !line.is_empty()).collect();
17+
if lines.is_empty() {
18+
return None;
19+
}
20+
21+
let operator_line = lines.last()?;
22+
let number_lines = &lines[..lines.len() - 1];
23+
24+
let max_width = lines.iter().map(|l| l.len()).max()?;
25+
let separators = find_separators(&lines, max_width);
26+
let ranges = get_ranges(&separators, max_width);
27+
28+
ranges
29+
.iter()
30+
.map(|&(start, end)| parse_problem(number_lines, operator_line, start, end))
31+
.collect()
32+
}
33+
34+
fn parse_problems_rtl(input: &str) -> Option<Vec<Problem>> {
35+
let lines: Vec<&str> = input.lines().filter(|line| !line.is_empty()).collect();
36+
if lines.is_empty() {
37+
return None;
38+
}
39+
40+
let operator_line = lines.last()?;
41+
let number_lines = &lines[..lines.len() - 1];
42+
43+
let max_width = lines.iter().map(|l| l.len()).max()?;
44+
let separators = find_separators(&lines, max_width);
45+
let ranges = get_ranges(&separators, max_width);
46+
47+
ranges
48+
.iter()
49+
.map(|&(start, end)| parse_problem_rtl(number_lines, operator_line, start, end))
50+
.collect()
51+
}
52+
53+
fn find_separators(lines: &[&str], max_width: usize) -> Vec<usize> {
54+
(0..max_width)
55+
.filter(|&col| lines.iter().all(|line| is_space(line, col)))
56+
.collect()
57+
}
58+
59+
fn is_space(line: &str, col: usize) -> bool {
60+
col >= line.len() || line.as_bytes().get(col) == Some(&b' ')
61+
}
62+
63+
fn get_ranges(separators: &[usize], max_width: usize) -> Vec<(usize, usize)> {
64+
if separators.is_empty() {
65+
return vec![(0, max_width)];
66+
}
67+
68+
let mut ranges = Vec::new();
69+
let mut start = 0;
70+
71+
for &sep in separators {
72+
if sep > start {
73+
ranges.push((start, sep));
74+
}
75+
start = sep + 1;
76+
}
77+
78+
if start < max_width {
79+
ranges.push((start, max_width));
80+
}
81+
82+
ranges
83+
}
84+
85+
fn parse_problem(
86+
number_lines: &[&str],
87+
operator_line: &str,
88+
start: usize,
89+
end: usize,
90+
) -> Option<Problem> {
91+
let operator = get_operator(operator_line, start, end)?;
92+
let numbers = get_numbers(number_lines, start, end);
93+
94+
if numbers.is_empty() {
95+
return None;
96+
}
97+
98+
Some(Problem { numbers, operator })
99+
}
100+
101+
fn parse_problem_rtl(
102+
number_lines: &[&str],
103+
operator_line: &str,
104+
start: usize,
105+
end: usize,
106+
) -> Option<Problem> {
107+
let operator = get_operator(operator_line, start, end)?;
108+
109+
let numbers: Vec<u64> = (start..end)
110+
.rev()
111+
.filter_map(|col| read_column(number_lines, col))
112+
.collect();
113+
114+
if numbers.is_empty() {
115+
return None;
116+
}
117+
118+
Some(Problem { numbers, operator })
119+
}
120+
121+
fn read_column(lines: &[&str], col: usize) -> Option<u64> {
122+
let digits: String = lines
123+
.iter()
124+
.filter_map(|line| {
125+
if col < line.len() {
126+
let ch = line.as_bytes()[col] as char;
127+
if ch.is_ascii_digit() { Some(ch) } else { None }
128+
} else {
129+
None
130+
}
131+
})
132+
.collect();
133+
134+
if digits.is_empty() {
135+
None
136+
} else {
137+
digits.parse().ok()
138+
}
139+
}
140+
141+
fn get_operator(line: &str, start: usize, end: usize) -> Option<char> {
142+
let end = end.min(line.len());
143+
if start >= end {
144+
return None;
145+
}
146+
147+
line[start..end].chars().find(|&ch| ch == '+' || ch == '*')
148+
}
149+
150+
fn get_numbers(lines: &[&str], start: usize, end: usize) -> Vec<u64> {
151+
lines
152+
.iter()
153+
.flat_map(|line| parse_line(line, start, end))
154+
.collect()
155+
}
156+
157+
fn parse_line(line: &str, start: usize, end: usize) -> Vec<u64> {
158+
if start >= line.len() {
159+
return Vec::new();
160+
}
161+
162+
let end = end.min(line.len());
163+
line[start..end]
164+
.split_whitespace()
165+
.filter_map(|s| s.parse().ok())
166+
.collect()
167+
}
168+
169+
fn solve_problem(problem: &Problem) -> u64 {
170+
match problem.operator {
171+
'+' => problem.numbers.iter().sum(),
172+
'*' => problem.numbers.iter().product(),
173+
_ => 0,
174+
}
175+
}
176+
177+
#[derive(Debug)]
178+
struct Problem {
179+
numbers: Vec<u64>,
180+
operator: char,
181+
}
182+
183+
#[cfg(test)]
184+
mod tests {
185+
use super::*;
186+
187+
#[test]
188+
fn test_part_one() {
189+
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
190+
assert_eq!(result, Some(4277556));
191+
}
192+
193+
#[test]
194+
fn test_part_two() {
195+
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
196+
assert_eq!(result, Some(3263827));
197+
}
198+
}

0 commit comments

Comments
 (0)