Advent of Code 2021
This year continue solving Advent Of Code in Rust! This is a note of some of my learnings / findings.
Reference
- https://github.com/0e4ef622/aoc (#1 in Rust discord leaderboard)
- https://github.com/malaire/advent-of-code (#3 in Rust discord leaderboard)
- https://github.com/detrumi/AdventOfCode (#6 in Rust discord leaderboard)
Preparation
Using the following script to download inputs:
#!/usr/local/bin/python3
import argparse
import subprocess
import os
SESSION = os.environ['AOC_SESSION']
parser = argparse.ArgumentParser(description='Read input')
parser.add_argument('day', type=int)
parser.add_argument('--year', type=int, default=2021)
args = parser.parse_args()
cmd = 'curl https://adventofcode.com/{}/day/{}/input --cookie "session={}"'.format(
args.year, args.day, SESSION)
output = subprocess.check_output(cmd, shell=True)
print(output.decode('utf-8'), end='')
where AOC_SESSION env variable is found in Network tab under headers: cookie -> session=X (see link).
Day 1
- Learned about
tuple_windowsfrom itertools crate to allow iteration over sliding window:
pub fn part2(lines: &Vec<String>) -> i64 {
let v = lines.iter().map(|l| parse_i64(l));
let sums = v.tuple_windows().map(|(a, b, c)| a + b + c);
sums.tuple_windows().filter(|(a, b)| a < b).count() as i64
}
Unfortunately tuple_windows has a limitation of size up to 4. Alternatively one can use slice::windows instead. This becomes a little more verbose as you need to collect elements into a vector before using this API.
pub fn part2(lines: &Vec<String>) -> i64 {
let v: Vec<_> = lines.iter().map(|l| parse_i64(l)).collect();
let sums: Vec<_> = v.windows(3).map(|a| a[0] + a[1] + a[2]).collect();
sums.windows(2).filter(|a| a[0] < a[1]).count() as i64
}
Day 3
- Rust has a built-in
from_str_radixmethod to convert a binary string to integer:
pub fn from_str_radix(src: &str, radix: u32) -> Result<i64, ParseIntError>
// Example:
i64::from_str_radix("111", 2).unwrap() == 7;
- Method
retainonVecto quickly filter some elements in-place:
// Example: will keep only elements in values that has i-th bit set to specified value
values.retain(|x| x[i] == bit);
Day 4
Stringhas a methodsplit_whitespaceto split ignoring any whitespace symbol (not just specific pattern):
let ints: Vec<_> = line.split_whitespace().map(|s| parse_i64(s)).collect();
Day 5
- Learned how to use
regexcrate to parse input:
// Example line:
// 30,443 -> 666,443
let re = Regex::new(r"(\d+),(\d+) -> (\d+),(\d+)").unwrap();
let cap = re.captures(line).unwrap();
let v: Vec<_> = (1..=4).map(|i| parse_i64(&cap[i])).collect();
vents.push((v[0], v[1], v[2], v[3]));
Day 6
- Recalled how to use map with default values in Rust. A little a bit more verbose than C++, but doesn’t seem like there is more concise way.
// next is a HashMap<i64, i64>
*next.entry(8).or_insert(0) += v;
Day 7
- Itertools has
minmax()trait to compute min and max simultaneously:
let (min, max) = crabs.iter().minmax().into_option().unwrap();
// instead of:
let min = crabs.iter().min().unwrap();
let max = crabs.iter().max().unwrap();
Day 8
- Itertools has
sorted()trait to sort an iterator:
pub fn sorted_digit(s: &str) -> String {
s.chars().sorted().collect()
}
Day 9
- Vectors support range indexing and there is
productmethod oniter:
// the same as: flat[0] * flat[1] * flat[2]
flat[0..3].iter().product()
Day 11
- Iter crate has
flattenmethod to simplify iteration over nested arrays:
// before:
for row in state.iter_mut() {
for value in row.iter_mut() {
*value += 1;
}
}
// after:
state.iter_mut().flatten().for_each(|x| *x += 1);
Day 12
- Using
or_default()is slightly shorter thanor_insert(Vec::new()), but it doesn’t infer the type.
// before:
let graph = HashMap::new();
graph.entry(a).or_insert(Vec::new()).push(b);
// after:
let mut graph: HashMap<_, Vec<_>> = HashMap::new();
graph.entry(a).or_default().push(b);
Day 13
- It is useful to implement my own
.cv()method on iterator to collect intovecwithout any type annotations:
pub trait CollectVec: Iterator + Sized {
fn cv(self) -> Vec<Self::Item> {
self.collect()
}
}
impl<I: Iterator> CollectVec for I {}
Day 14
- Itertools has
countsmethod to aggregate the iterator into hashmap with occurences
// counts the number of consecutive 2-letter substrings
let mut pairs: HashMap<String, usize> = input.windows(2).map(|x| x.iter().collect()).counts();
Day 15
BinaryHeapandReverseto turn this into a min-heap:
let mut heap = BinaryHeap::new();
// Wrap values in `Reverse`
heap.push(Reverse(1));
heap.push(Reverse(5));
// If we pop these scores now, they should come back in the reverse order.
assert_eq!(heap.pop(), Some(Reverse(1)));
assert_eq!(heap.pop(), Some(Reverse(2)));
Day 16
char.to_digit(radix)will convert from char representation to a number
'A'.to_digit(16) // returns Some(10)
- Format string
{:04b}to convert from a number to binary representation with leading digits:
format!("{:04b}", 5) // returns "0101"
Day 18
- Most efficient and universal method in replacing the range of the vector with other elements is
splice. This is the example of replacing the splitted value with another pair in day18 problem:
let replace = [Token::Open, Token::Value(a), Token::Value(b), Token::Close];
now.splice(index..=index, replace);