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_windows
from 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_radix
method 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
retain
onVec
to 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
String
has a methodsplit_whitespace
to 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
regex
crate 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
product
method oniter
:
// the same as: flat[0] * flat[1] * flat[2]
flat[0..3].iter().product()
Day 11
- Iter crate has
flatten
method 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 intovec
without 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
counts
method 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
BinaryHeap
andReverse
to 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);