Skip to content

Commit 94e4277

Browse files
committed
Year 2015 speed and code quality improvements
1 parent c8df551 commit 94e4277

File tree

18 files changed

+244
-253
lines changed

18 files changed

+244
-253
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -370,14 +370,14 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
370370
| 5 | [Doesn't He Have Intern-Elves For This?](https://adventofcode.com/2015/day/5) | [Source](src/year2015/day05.rs) | 38 |
371371
| 6 | [Probably a Fire Hazard](https://adventofcode.com/2015/day/6) | [Source](src/year2015/day06.rs) | 6572 |
372372
| 7 | [Some Assembly Required](https://adventofcode.com/2015/day/7) | [Source](src/year2015/day07.rs) | 27 |
373-
| 8 | [Matchsticks](https://adventofcode.com/2015/day/8) | [Source](src/year2015/day08.rs) | 12 |
373+
| 8 | [Matchsticks](https://adventofcode.com/2015/day/8) | [Source](src/year2015/day08.rs) | 5 |
374374
| 9 | [All in a Single Night](https://adventofcode.com/2015/day/9) | [Source](src/year2015/day09.rs) | 34 |
375-
| 10 | [Elves Look, Elves Say](https://adventofcode.com/2015/day/10) | [Source](src/year2015/day10.rs) | 15 |
375+
| 10 | [Elves Look, Elves Say](https://adventofcode.com/2015/day/10) | [Source](src/year2015/day10.rs) | 17 |
376376
| 11 | [Corporate Policy](https://adventofcode.com/2015/day/11) | [Source](src/year2015/day11.rs) | 1 |
377-
| 12 | [JSAbacusFramework.io](https://adventofcode.com/2015/day/12) | [Source](src/year2015/day12.rs) | 83 |
377+
| 12 | [JSAbacusFramework.io](https://adventofcode.com/2015/day/12) | [Source](src/year2015/day12.rs) | 71 |
378378
| 13 | [Knights of the Dinner Table](https://adventofcode.com/2015/day/13) | [Source](src/year2015/day13.rs) | 37 |
379-
| 14 | [Reindeer Olympics](https://adventofcode.com/2015/day/14) | [Source](src/year2015/day14.rs) | 28 |
380-
| 15 | [Science for Hungry People](https://adventofcode.com/2015/day/15) | [Source](src/year2015/day15.rs) | 53 |
379+
| 14 | [Reindeer Olympics](https://adventofcode.com/2015/day/14) | [Source](src/year2015/day14.rs) | 24 |
380+
| 15 | [Science for Hungry People](https://adventofcode.com/2015/day/15) | [Source](src/year2015/day15.rs) | 43 |
381381
| 16 | [Aunt Sue](https://adventofcode.com/2015/day/16) | [Source](src/year2015/day16.rs) | 20 |
382382
| 17 | [No Such Thing as Too Much](https://adventofcode.com/2015/day/17) | [Source](src/year2015/day17.rs) | 45 |
383383
| 18 | [Like a GIF For Your Yard](https://adventofcode.com/2015/day/18) | [Source](src/year2015/day18.rs) | 154 |
@@ -386,7 +386,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
386386
| 21 | [RPG Simulator 20XX](https://adventofcode.com/2015/day/21) | [Source](src/year2015/day21.rs) | 2 |
387387
| 22 | [Wizard Simulator 20XX](https://adventofcode.com/2015/day/22) | [Source](src/year2015/day22.rs) | 235 |
388388
| 23 | [Opening the Turing Lock](https://adventofcode.com/2015/day/23) | [Source](src/year2015/day23.rs) | 6 |
389-
| 24 | [It Hangs in the Balance](https://adventofcode.com/2015/day/24) | [Source](src/year2015/day24.rs) | 380 |
389+
| 24 | [It Hangs in the Balance](https://adventofcode.com/2015/day/24) | [Source](src/year2015/day24.rs) | 237 |
390390
| 25 | [Let It Snow](https://adventofcode.com/2015/day/25) | [Source](src/year2015/day25.rs) | 1 |
391391

392392
[checks-badge]: https://img.shields.io/github/actions/workflow/status/maneatingape/advent-of-code-rust/checks.yml?label=checks

src/year2015/day01.rs

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
//! # Not Quite Lisp
22
//!
33
//! The input is first converted into bytes. This is safe as it contains only ASCII characters.
4-
//! Then each parenthesis is parsed into either +1 or -1, treating the trailing newline
5-
//! as a special case of 0.
4+
//! Then each parenthesis is parsed into either +1 or -1.
65
pub fn parse(input: &str) -> Vec<i32> {
7-
fn helper(b: u8) -> i32 {
8-
match b {
9-
b'(' => 1,
10-
b')' => -1,
11-
_ => 0,
12-
}
13-
}
14-
input.bytes().map(helper).collect()
6+
input.trim().bytes().map(|b| if b == b'(' { 1 } else { -1 }).collect()
157
}
168

179
pub fn part1(input: &[i32]) -> i32 {
@@ -20,13 +12,12 @@ pub fn part1(input: &[i32]) -> i32 {
2012

2113
pub fn part2(input: &[i32]) -> usize {
2214
let mut floor = 0;
23-
24-
for (i, x) in input.iter().enumerate() {
25-
floor += x;
26-
if floor < 0 {
27-
return i + 1;
28-
}
29-
}
30-
31-
unreachable!()
15+
input
16+
.iter()
17+
.position(|&b| {
18+
floor += b;
19+
floor < 0
20+
})
21+
.map(|i| i + 1)
22+
.unwrap()
3223
}

src/year2015/day02.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
use crate::util::iter::*;
1212
use crate::util::parse::*;
1313

14-
type Gift = [u32; 3];
14+
type Input = Vec<[u32; 3]>;
1515

16-
pub fn parse(input: &str) -> Vec<Gift> {
16+
pub fn parse(input: &str) -> Input {
1717
input
1818
.iter_unsigned()
1919
.chunk::<3>()
@@ -25,10 +25,10 @@ pub fn parse(input: &str) -> Vec<Gift> {
2525
.collect()
2626
}
2727

28-
pub fn part1(input: &[Gift]) -> u32 {
28+
pub fn part1(input: &Input) -> u32 {
2929
input.iter().map(|[l, w, h]| 2 * (l * w + w * h + h * l) + l * w).sum()
3030
}
3131

32-
pub fn part2(input: &[Gift]) -> u32 {
32+
pub fn part2(input: &Input) -> u32 {
3333
input.iter().map(|[l, w, h]| 2 * (l + w) + (l * w * h)).sum()
3434
}

src/year2015/day03.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,16 @@ pub fn part2(input: &[Point]) -> usize {
2424
fn deliver(input: &[Point], predicate: fn(usize) -> bool) -> usize {
2525
let mut santa = ORIGIN;
2626
let mut robot = ORIGIN;
27-
let mut set = FastSet::with_capacity(10_000);
27+
28+
let mut set = FastSet::with_capacity(input.len());
2829
set.insert(ORIGIN);
2930

30-
for (index, point) in input.iter().enumerate() {
31+
for (index, &point) in input.iter().enumerate() {
3132
if predicate(index) {
32-
santa += *point;
33+
santa += point;
3334
set.insert(santa);
3435
} else {
35-
robot += *point;
36+
robot += point;
3637
set.insert(robot);
3738
}
3839
}

src/year2015/day05.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,30 @@ pub fn parse(input: &str) -> Vec<&[u8]> {
2929
}
3030

3131
pub fn part1(input: &[&[u8]]) -> usize {
32+
// Bitmask for vowels (a, e, i, o, u)
33+
const VOWEL_MASK: u32 = 0x0104111;
34+
// Bitmask for forbidden pairs
35+
const FORBIDDEN_MASK: u32 = 0x101000a;
36+
3237
let nice = |line: &&&[u8]| {
3338
let mut vowels = 0;
3439
let mut pairs = 0;
3540
let mut previous = 0;
3641

37-
for c in line.iter() {
42+
for &c in line.iter() {
3843
let current = 1 << (c - b'a');
39-
if 0x101000a & current & (previous << 1) != 0 {
44+
45+
if FORBIDDEN_MASK & current & (previous << 1) != 0 {
4046
return false;
4147
}
42-
if 0x0104111 & current != 0 {
48+
if VOWEL_MASK & current != 0 {
4349
vowels += 1;
4450
}
4551
if previous == current {
4652
pairs += 1;
47-
} else {
48-
previous = current;
4953
}
54+
55+
previous = current;
5056
}
5157

5258
vowels >= 3 && pairs >= 1
@@ -73,8 +79,10 @@ pub fn part2(input: &[&[u8]]) -> usize {
7379
let delta = position - pairs[index];
7480

7581
if delta > offset {
82+
// This is the first time we've seen the pair for this string.
7683
pairs[index] = position;
7784
} else if delta > 1 {
85+
// No overlapping means that the distance must be at least two.
7886
two_pair = true;
7987
}
8088
if first == third {

src/year2015/day08.rs

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,53 @@
11
//! # Matchsticks
22
//!
33
//! While [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) may feel like a
4-
//! natural choice, it's much faster and easier to simply treat the input as a stream of raw
5-
//! ASCII `u8` bytes including newlines.
4+
//! natural choice, it's much faster and easier to simply treat the input as a single stream of raw
5+
//! ASCII `u8` bytes without splitting line by line.
66
//!
7-
//! For part one we run a small state machine using [`fold`] to keep track of the current and
8-
//! previous characters. If we encounter a hexadecimal escape then four characters become one so the
9-
//! difference increases by three. The sequences `\\` and `\"` both increase the difference by one.
10-
//! Each newline increases the difference by two since every line is enclosed with two quotes.
7+
//! For part one we skip over the first quote of each line. The last quote on each line increases
8+
//! the difference by two since every line is enclosed with two quotes. If we encounter a
9+
//! hexadecimal escape then four characters become one so the difference increases by three.
10+
//! The sequences `\\` and `\"` both increase the difference by one.
1111
//!
12-
//! Part two is even more straightforward with no need for statekeeping. Quotes and backslashes
13-
//! need to be escaped so increase the difference by one. As before each newline increases by the
14-
//! difference by two.
15-
//!
16-
//! [`fold`]: Iterator::fold
17-
const NEWLINE: u8 = 10;
18-
const QUOTE: u8 = 34;
19-
const SLASH: u8 = 92;
20-
const ESCAPE: u8 = 120;
12+
//! Part two is even more straightforward. Quotes and backslashes need to be escaped so increase
13+
//! the difference by one. Each newline increases by the difference by two.
14+
const NEWLINE: u8 = b'\n';
15+
const QUOTE: u8 = b'\"';
16+
const SLASH: u8 = b'\\';
17+
const ESCAPE: u8 = b'x';
2118

22-
pub fn parse(input: &str) -> &str {
23-
input
19+
pub fn parse(input: &str) -> &[u8] {
20+
input.as_bytes()
2421
}
2522

26-
pub fn part1(input: &str) -> u32 {
27-
let (_, result) = input.bytes().fold((false, 0), |(flag, count), b| match (flag, b) {
28-
(true, ESCAPE) => (false, count + 3),
29-
(true, _) => (false, count + 1),
30-
(false, SLASH) => (true, count),
31-
(false, NEWLINE) => (false, count + 2),
32-
_ => (false, count),
33-
});
23+
pub fn part1(input: &[u8]) -> usize {
24+
// Skip very first quote to prevent double counting.
25+
let mut index = 1;
26+
let mut result = 0;
27+
28+
while index < input.len() {
29+
let skip = match input[index] {
30+
SLASH => match input[index + 1] {
31+
ESCAPE => 4,
32+
_ => 2,
33+
},
34+
QUOTE => 3,
35+
_ => 1,
36+
};
37+
result += skip - 1;
38+
index += skip;
39+
}
40+
3441
result
3542
}
3643

37-
pub fn part2(input: &str) -> u32 {
44+
pub fn part2(input: &[u8]) -> u32 {
3845
input
39-
.bytes()
40-
.map(|b| match b {
46+
.iter()
47+
.map(|&b| match b {
48+
// Escape special characters.
4149
QUOTE | SLASH => 1,
50+
// Each line needs two enclosing quotes.
4251
NEWLINE => 2,
4352
_ => 0,
4453
})

src/year2015/day09.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,11 @@ pub fn parse(input: &str) -> Result {
6565
local_max = local_max.max(distance);
6666
};
6767

68+
// First trip.
6869
trip(0, slice[0]);
70+
// Last trip.
6971
trip(0, slice[slice.len() - 1]);
70-
72+
// Intermediate trips.
7173
for i in 1..slice.len() {
7274
trip(slice[i], slice[i - 1]);
7375
}

src/year2015/day10.rs

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
//!
1313
//! Computing the result is simply multiplying the number of each element by its length. There are
1414
//! 92 elements total so we can use a fixed size array to store the decay chain information.
15+
//!
16+
//! It would be possible (but less fun) to precompute all possible 92 answers into a
17+
//! look up table.
1518
use crate::util::hash::*;
1619

1720
const ELEMENTS: &str = "\
@@ -113,34 +116,34 @@ type Result = (usize, usize);
113116
pub fn parse(input: &str) -> Result {
114117
let elements: Vec<Vec<_>> =
115118
ELEMENTS.lines().map(|line| line.split_ascii_whitespace().collect()).collect();
116-
let mut indices = FastMap::with_capacity(92);
119+
let mut indices = FastMap::with_capacity(92 * 2);
117120

121+
// Map both sequence and element name to indices.
118122
for (i, tokens) in elements.iter().enumerate() {
123+
indices.insert(tokens[0], i);
119124
indices.insert(tokens[2], i);
120125
}
121126

122-
let mut sequence = [""; 92];
123-
let mut decays = [[None; 6]; 92];
127+
// Build list of decay chains.
128+
let sizes: Vec<_> = elements.iter().map(|e| e[0].len()).collect();
129+
let decays: Vec<Vec<_>> =
130+
elements.iter().map(|e| e[4..].iter().map(|t| indices[t]).collect()).collect();
124131

125-
for (i, tokens) in elements.iter().enumerate() {
126-
sequence[i] = tokens[0];
127-
for (j, &token) in tokens.iter().skip(4).enumerate() {
128-
decays[i][j] = Some(indices[token]);
129-
}
130-
}
132+
// Each input is a single element.
133+
let mut current = [0; 92];
134+
current[indices[input.trim()]] = 1;
131135

132-
let mut current = initial_state(input, &sequence);
133136
for _ in 0..40 {
134137
current = step(&current, &decays);
135138
}
139+
let part1 = length(&current, &sizes);
136140

137-
let result1 = length(&current, &sequence);
138141
for _ in 0..10 {
139142
current = step(&current, &decays);
140143
}
144+
let part2 = length(&current, &sizes);
141145

142-
let result2 = length(&current, &sequence);
143-
(result1, result2)
146+
(part1, part2)
144147
}
145148

146149
pub fn part1(input: &Result) -> usize {
@@ -151,31 +154,20 @@ pub fn part2(input: &Result) -> usize {
151154
input.1
152155
}
153156

154-
fn initial_state(input: &str, sequence: &[&str]) -> [usize; 92] {
155-
let input = input.trim();
156-
let start = sequence.iter().position(|&s| s == input).unwrap();
157-
158-
let mut current = [0; 92];
159-
current[start] += 1;
160-
current
161-
}
162-
163-
fn step(current: &[usize; 92], decays: &[[Option<usize>; 6]; 92]) -> [usize; 92] {
157+
fn step(current: &[usize], decays: &[Vec<usize>]) -> [usize; 92] {
164158
let mut next = [0; 92];
165159

166-
for i in 0..92 {
167-
let c = current[i];
168-
if c > 0 {
169-
let mut iter = decays[i].iter();
170-
while let Some(Some(index)) = iter.next() {
171-
next[*index] += c;
160+
for (i, &count) in current.iter().enumerate() {
161+
if count > 0 {
162+
for &element in &decays[i] {
163+
next[element] += count;
172164
}
173165
}
174166
}
175167

176168
next
177169
}
178170

179-
fn length(current: &[usize; 92], sequence: &[&str; 92]) -> usize {
180-
current.iter().zip(sequence.iter()).map(|(c, s)| c * s.len()).sum()
171+
fn length(current: &[usize], sizes: &[usize]) -> usize {
172+
current.iter().zip(sizes.iter()).map(|(c, s)| c * s).sum()
181173
}

src/year2015/day11.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,10 @@ pub fn part2(input: &Input) -> &str {
4848
/// Sanitize the input to make sure it has no invalid characters. We increment the first invalid
4949
/// character found, for example `abcixyz` becomes `abcjaaa`.
5050
fn clean(mut password: Password) -> Password {
51-
let mut reset = false;
52-
53-
for digit in &mut password {
54-
if reset {
55-
*digit = b'a';
56-
} else if matches!(digit, b'i' | b'o' | b'l') {
57-
*digit += 1;
58-
reset = true;
59-
}
51+
if let Some(index) = password.iter().position(|&d| matches!(d, b'i' | b'o' | b'l')) {
52+
password[index] += 1;
53+
password[index + 1..].fill(b'a');
6054
}
61-
6255
password
6356
}
6457

src/year2015/day12.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ fn parse_object(input: &[u8], start: usize) -> Result {
7575
let mut ignore = false;
7676

7777
while input[index] != b'}' {
78-
let Result { next: first, .. } = parse_json(input, index + 1);
78+
let Result { next: first, .. } = parse_string(input, index + 1);
7979
let Result { next: second, ignore: red, value } = parse_json(input, first + 1);
8080
index = second;
8181
total += value;

0 commit comments

Comments
 (0)