Skip to content

Commit 73acfe5

Browse files
committed
Year 2016 speed and code quality improvements
1 parent 94e4277 commit 73acfe5

File tree

8 files changed

+135
-135
lines changed

8 files changed

+135
-135
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,10 +336,10 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
336336
| 3 | [Squares With Three Sides](https://adventofcode.com/2016/day/3) | [Source](src/year2016/day03.rs) | 24 |
337337
| 4 | [Security Through Obscurity](https://adventofcode.com/2016/day/4) | [Source](src/year2016/day04.rs) | 79 |
338338
| 5 | [How About a Nice Game of Chess?](https://adventofcode.com/2016/day/5) | [Source](src/year2016/day05.rs) | 37000 |
339-
| 6 | [Signals and Noise](https://adventofcode.com/2016/day/6) | [Source](src/year2016/day06.rs) | 4 |
339+
| 6 | [Signals and Noise](https://adventofcode.com/2016/day/6) | [Source](src/year2016/day06.rs) | 3 |
340340
| 7 | [Internet Protocol Version 7](https://adventofcode.com/2016/day/7) | [Source](src/year2016/day07.rs) | 364 |
341341
| 8 | [Two-Factor Authentication](https://adventofcode.com/2016/day/8) | [Source](src/year2016/day08.rs) | 9 |
342-
| 9 | [Explosives in Cyberspace](https://adventofcode.com/2016/day/9) | [Source](src/year2016/day09.rs) | 6 |
342+
| 9 | [Explosives in Cyberspace](https://adventofcode.com/2016/day/9) | [Source](src/year2016/day09.rs) | 7 |
343343
| 10 | [Balance Bots](https://adventofcode.com/2016/day/10) | [Source](src/year2016/day10.rs) | 16 |
344344
| 11 | [Radioisotope Thermoelectric Generators](https://adventofcode.com/2016/day/11) | [Source](src/year2016/day11.rs) | 719 |
345345
| 12 | [Leonardo's Monorail](https://adventofcode.com/2016/day/12) | [Source](src/year2016/day12.rs) | 1 |
@@ -348,7 +348,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
348348
| 15 | [Timing is Everything](https://adventofcode.com/2016/day/15) | [Source](src/year2016/day15.rs) | 1 |
349349
| 16 | [Dragon Checksum](https://adventofcode.com/2016/day/16) | [Source](src/year2016/day16.rs) | 1 |
350350
| 17 | [Two Steps Forward](https://adventofcode.com/2016/day/17) | [Source](src/year2016/day17.rs) | 3858 |
351-
| 18 | [Like a Rogue](https://adventofcode.com/2016/day/18) | [Source](src/year2016/day18.rs) | 728 |
351+
| 18 | [Like a Rogue](https://adventofcode.com/2016/day/18) | [Source](src/year2016/day18.rs) | 400 |
352352
| 19 | [An Elephant Named Joseph](https://adventofcode.com/2016/day/19) | [Source](src/year2016/day19.rs) | 1 |
353353
| 20 | [Firewall Rules](https://adventofcode.com/2016/day/20) | [Source](src/year2016/day20.rs) | 21 |
354354
| 21 | [Scrambled Letters and Hash](https://adventofcode.com/2016/day/21) | [Source](src/year2016/day21.rs) | 10 |

src/year2016/day03.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ pub fn parse(input: &str) -> Vec<u32> {
1313
}
1414

1515
pub fn part1(input: &[u32]) -> usize {
16-
count(input.iter().copied())
16+
count(input.iter())
1717
}
1818

1919
pub fn part2(input: &[u32]) -> usize {
20-
let first = count(input.iter().copied().step_by(3));
21-
let second = count(input.iter().copied().skip(1).step_by(3));
22-
let third = count(input.iter().copied().skip(2).step_by(3));
20+
let first = count(input.iter().step_by(3));
21+
let second = count(input.iter().skip(1).step_by(3));
22+
let third = count(input.iter().skip(2).step_by(3));
2323
first + second + third
2424
}
2525

26-
fn count(iter: impl Iterator<Item = u32>) -> usize {
27-
iter.chunk::<3>().filter(|&[a, b, c]| a + b > c && a + c > b && b + c > a).count()
26+
fn count<'a, I>(iter: I) -> usize
27+
where
28+
I: Iterator<Item = &'a u32>,
29+
{
30+
iter.chunk::<3>().filter(|&[&a, &b, &c]| a + b > c && a + c > b && b + c > a).count()
2831
}

src/year2016/day04.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub fn parse(input: &str) -> Vec<Room<'_>> {
2828
// Count the frequency of each digit, the frequency of each frequency `fof` and the
2929
// highest total frequency.
3030
let mut freq = [0; 26];
31-
let mut fof = [0; 64];
31+
let mut fof = [0; 26];
3232
let mut highest = 0;
3333

3434
for b in name.bytes() {

src/year2016/day06.rs

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,35 @@
22
//!
33
//! The cardinality of uppercase letters is only 26 so we can use a fixed size array to
44
//! count the frequency of each character efficiently.
5-
type Input = Vec<[u32; 26]>;
5+
type Input = (String, String);
66

77
pub fn parse(input: &str) -> Input {
88
let width = input.lines().next().unwrap().len();
9-
let mut freq = vec![[0; 26]; width];
9+
let stride = width + 1;
10+
let input = input.as_bytes();
1011

11-
for (i, b) in input.bytes().filter(u8::is_ascii_lowercase).enumerate() {
12-
freq[i % width][(b - b'a') as usize] += 1;
13-
}
12+
let to_index = |b: u8| (b - b'a') as usize;
13+
let to_char = |i: usize| ((i as u8) + b'a') as char;
1414

15-
freq
16-
}
15+
(0..width)
16+
.map(|offset| {
17+
let mut freq = [0; 26];
18+
input.iter().skip(offset).step_by(stride).for_each(|&b| freq[to_index(b)] += 1);
19+
20+
let (max, _) =
21+
freq.iter().enumerate().filter(|(_, f)| **f > 0).max_by_key(|(_, f)| **f).unwrap();
22+
let (min, _) =
23+
freq.iter().enumerate().filter(|(_, f)| **f > 0).min_by_key(|(_, f)| **f).unwrap();
1724

18-
pub fn part1(input: &Input) -> String {
19-
find(input, |freq| {
20-
freq.iter().enumerate().filter(|(_, f)| **f > 0).max_by_key(|(_, f)| **f).unwrap()
21-
})
25+
(to_char(max), to_char(min))
26+
})
27+
.unzip()
2228
}
2329

24-
pub fn part2(input: &Input) -> String {
25-
find(input, |freq| {
26-
freq.iter().enumerate().filter(|(_, f)| **f > 0).min_by_key(|(_, f)| **f).unwrap()
27-
})
30+
pub fn part1(input: &Input) -> &str {
31+
&input.0
2832
}
2933

30-
fn find(input: &Input, ec: impl Fn(&[u32; 26]) -> (usize, &u32)) -> String {
31-
input.iter().map(ec).map(|(index, _)| ((index as u8) + b'a') as char).collect()
34+
pub fn part2(input: &Input) -> &str {
35+
&input.1
3236
}

src/year2016/day07.rs

Lines changed: 51 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,73 @@
11
//! # Internet Protocol Version 7
22
//!
3-
//! It's faster to treat the entire input as one big stream, using line breaks to increment
4-
//! the count if an address is valid.
5-
//!
63
//! For part two there are at most 26 * 26 = 676 possible ABA or BAB sequences so we can use
74
//! a fixed size array to keep track of which ones we've seen for the current address so far.
8-
pub fn parse(input: &str) -> &[u8] {
9-
input.as_bytes()
5+
pub fn parse(input: &str) -> Vec<&[u8]> {
6+
input.lines().map(str::as_bytes).collect()
107
}
118

12-
pub fn part1(input: &[u8]) -> usize {
13-
let mut count = 0;
14-
let mut inside = false;
15-
let mut positive = false;
16-
let mut negative = false;
9+
pub fn part1(input: &[&[u8]]) -> usize {
10+
input
11+
.iter()
12+
.filter(|line| {
13+
let mut has_abba = false;
14+
let mut inside_brackets = false;
1715

18-
for w in input.windows(4) {
19-
if w[0].is_ascii_lowercase() {
20-
if w[0] == w[3] && w[1] == w[2] && w[0] != w[1] {
21-
if inside {
22-
negative = true;
16+
for w in line.windows(4) {
17+
if w[0].is_ascii_lowercase() {
18+
if w[0] == w[3] && w[1] == w[2] && w[0] != w[1] {
19+
if inside_brackets {
20+
return false;
21+
}
22+
has_abba = true;
23+
}
2324
} else {
24-
positive = true;
25+
inside_brackets = w[0] == b'[';
2526
}
2627
}
27-
} else if w[0] == b'[' {
28-
inside = true;
29-
} else if w[0] == b']' {
30-
inside = false;
31-
} else {
32-
// Next line
33-
if positive && !negative {
34-
count += 1;
35-
}
36-
positive = false;
37-
negative = false;
38-
}
39-
}
4028

41-
if positive && !negative { count + 1 } else { count }
29+
has_abba
30+
})
31+
.count()
4232
}
4333

44-
pub fn part2(input: &[u8]) -> usize {
45-
let mut count = 0;
46-
let mut version = 0;
47-
let mut inside = false;
48-
let mut positive = false;
34+
pub fn part2(input: &[&[u8]]) -> usize {
4935
let mut aba = [usize::MAX; 676];
5036
let mut bab = [usize::MAX; 676];
5137

52-
for w in input.windows(3) {
53-
if w[1].is_ascii_lowercase() {
54-
if w[0] == w[2] && w[0] != w[1] {
55-
let first = (w[0] - b'a') as usize;
56-
let second = (w[1] - b'a') as usize;
38+
input
39+
.iter()
40+
.enumerate()
41+
.filter(|&(version, line)| {
42+
let mut inside_brackets = false;
5743

58-
if inside {
59-
// Reverse the order of letters
60-
let index = 26 * second + first;
61-
bab[index] = version;
62-
positive |= aba[index] == version;
44+
for w in line.windows(3) {
45+
if w[0].is_ascii_lowercase() {
46+
if w[0] == w[2] && w[0] != w[1] && w[1].is_ascii_lowercase() {
47+
let first = (w[0] - b'a') as usize;
48+
let second = (w[1] - b'a') as usize;
49+
50+
if inside_brackets {
51+
// Reverse the order of letters
52+
let index = 26 * second + first;
53+
bab[index] = version;
54+
if aba[index] == version {
55+
return true;
56+
}
57+
} else {
58+
let index = 26 * first + second;
59+
aba[index] = version;
60+
if bab[index] == version {
61+
return true;
62+
}
63+
}
64+
}
6365
} else {
64-
let index = 26 * first + second;
65-
aba[index] = version;
66-
positive |= bab[index] == version;
66+
inside_brackets = w[0] == b'[';
6767
}
6868
}
69-
} else if w[1] == b'[' {
70-
inside = true;
71-
} else if w[1] == b']' {
72-
inside = false;
73-
} else {
74-
// Next line
75-
if positive {
76-
count += 1;
77-
}
78-
version += 1;
79-
positive = false;
80-
}
81-
}
8269

83-
if positive { count + 1 } else { count }
70+
false
71+
})
72+
.count()
8473
}

src/year2016/day09.rs

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,35 @@ pub fn part2(input: &[u8]) -> usize {
1515
decompress(input, true)
1616
}
1717

18-
fn decompress(mut slice: &[u8], recurse: bool) -> usize {
18+
fn decompress(mut slice: &[u8], part_two: bool) -> usize {
1919
let mut length = 0;
2020

21-
while !slice.is_empty() {
22-
if slice[0] == b'(' {
23-
let (next, amount) = number(slice);
24-
let (next, repeat) = number(next);
25-
26-
let start = 1;
27-
let end = start + amount;
28-
let result = if recurse { decompress(&next[start..end], true) } else { amount };
29-
30-
slice = &next[end..];
31-
length += result * repeat;
32-
} else {
33-
slice = &slice[1..];
34-
length += 1;
35-
}
21+
// Find the next marker.
22+
while let Some(start) = slice.iter().position(|&b| b == b'(') {
23+
let (next, amount) = number(&slice[start + 1..]);
24+
let (next, repeat) = number(next);
25+
26+
// For part two, recursively decompress data.
27+
let result = if part_two { decompress(&next[..amount], true) } else { amount };
28+
29+
slice = &next[amount..];
30+
length += start + result * repeat;
3631
}
3732

38-
length
33+
// Add remaining plain data that doesn't container any marker.
34+
length + slice.len()
3935
}
4036

4137
fn number(slice: &[u8]) -> (&[u8], usize) {
42-
// Parse number digit by digit, skipping over the delimeter at the start but leaving the
43-
// delimeter at the end.
44-
let mut index = 2;
45-
let mut acc = slice[1].to_decimal() as usize;
38+
// Parse number digit by digit.
39+
let mut index = 0;
40+
let mut acc = 0;
4641

4742
while slice[index].is_ascii_digit() {
4843
acc = 10 * acc + slice[index].to_decimal() as usize;
4944
index += 1;
5045
}
5146

52-
(&slice[index..], acc)
47+
// Skip over trailing delimeter.
48+
(&slice[index + 1..], acc)
5349
}

src/year2016/day18.rs

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,57 @@
11
//! # Like a Rogue
22
//!
3-
//! We represent a trap with a 1 bit and a safe tile with a 0 bit storing the entire row
4-
//! in a [`u128`]. We then use bitwise logic to calculate the next row.
5-
//!
3+
//! We represent a trap with a 1 bit and a safe tile with a 0 bit.
64
//! Writing out the [truth table](https://en.wikipedia.org/wiki/Truth_table) for the rules:
75
//!
8-
//! | Left | Center | Right |
9-
//! | ---- | ------ | ----- |
10-
//! | 1 | 1 | 0 |
11-
//! | 0 | 1 | 1 |
12-
//! | 1 | 0 | 0 |
13-
//! | 0 | 0 | 1 |
6+
//! | Left | Center | Right | Output |
7+
//! | ---- | ------ | ----- | ------ |
8+
//! | 1 | 1 | 0 | 1 |
9+
//! | 0 | 1 | 1 | 1 |
10+
//! | 1 | 0 | 0 | 1 |
11+
//! | 0 | 0 | 1 | 1 |
12+
//! | 1 | 1 | 1 | 0 |
13+
//! | 0 | 1 | 0 | 0 |
14+
//! | 1 | 0 | 1 | 0 |
15+
//! | 0 | 0 | 0 | 0 |
1416
//!
1517
//! We can see that the value of the center doesn't matter and that the next tile will be a trap
1618
//! if the left and right values are different. We calculate this for all traps at the same time
1719
//! with a bitwise [XOR](https://en.wikipedia.org/wiki/XOR_gate).
20+
//!
21+
//! Since even columns depend only on odd columns and vice-versa, we split the input into two,
22+
//! storing each half using 50 bits of a `u64`.
1823
pub fn parse(input: &str) -> &str {
1924
input.trim()
2025
}
2126

22-
pub fn part1(input: &str) -> u32 {
27+
pub fn part1(input: &str) -> usize {
2328
count(input, 40)
2429
}
2530

26-
pub fn part2(input: &str) -> u32 {
31+
pub fn part2(input: &str) -> usize {
2732
count(input, 400_000)
2833
}
2934

30-
fn count(input: &str, rows: u32) -> u32 {
31-
let width = input.len() as u32;
32-
// We don't use the full 128 bit width so create a mask the same width as the input
33-
// to prevent bits spilling over.
34-
let mask = (1 << width) - 1;
35-
35+
fn count(input: &str, rows: usize) -> usize {
36+
// We don't use all the bits in each `u64` so create a mask the same width as
37+
// half the input to prevent bits spilling over.
38+
let mask = (1 << (input.len() / 2)) - 1;
3639
// Represent each trap as a `1` bit.
40+
let traps = |acc: u64, b: u8| (acc << 1) | (b == b'^') as u64;
41+
42+
// Split traps into two halves.
43+
let mut even = input.bytes().step_by(2).fold(0, traps);
44+
let mut odd = input.bytes().skip(1).step_by(2).fold(0, traps);
3745
let mut total = 0;
38-
let mut row = input.bytes().fold(0, |acc, b| (acc << 1) | (b == b'^') as u128);
3946

4047
for _ in 0..rows {
4148
// Count the traps in each row.
42-
total += row.count_ones();
43-
// Only consider the left and right values for the next row.
44-
row = ((row << 1) ^ (row >> 1)) & mask;
49+
total += even.count_ones() + odd.count_ones();
50+
51+
// Calculate the next row of even traps from odd traps and vice-versa.
52+
(even, odd) = (odd ^ (odd >> 1), even ^ ((even << 1) & mask));
4553
}
4654

4755
// We want the number of safe tiles so convert from the number of traps.
48-
rows * width - total
56+
input.len() * rows - total as usize
4957
}

tests/year2016/day07.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ zazbz[bzb]cdb";
1515
#[test]
1616
fn part1_test() {
1717
let input = parse(FIRST_EXAMPLE);
18-
assert_eq!(part1(input), 2);
18+
assert_eq!(part1(&input), 2);
1919
}
2020

2121
#[test]
2222
fn part2_test() {
2323
let input = parse(SECOND_EXAMPLE);
24-
assert_eq!(part2(input), 3);
24+
assert_eq!(part2(&input), 3);
2525
}

0 commit comments

Comments
 (0)