This is a collection of compiled solutions for Advent of Code 2021 as the puzzles were solved originally. The solutions are written in Rust.
I have decided to use this opportunity to learn about the Rust programming language.
Since I hope to learn something new with each puzzle, I'm keeping all solutions mostly untouched after the result submission.
This will allow me to consider the progress I made between each puzzle.
Since I might get new insights or ideas for an old solution later down the road, I'll keep them in a separate file, snippets.rs.
- Reading a text file into a
String:std::fs::read_to_string
- Iterator over the lines in a
String:s.lines() - Map a function/closure over an iterator, discard failures:
it.filter_map(f) - Type hint only for outer type parameter:
let y: Vec<_> = x.split(" ").collect();let y = x.split(" ").collect::Vec<_>();
- Chain fallible operations with
and_then:y[1].parse::<i32>().ok().and_then(|n| y[0].chars().nth(0).and_then(|c| Some((c, n))))
ifstatement as ternary operator:counts[i] += if chars.next() == Some('1') { 1 } else { -1 }- Parse numbers with different bases:
i32::from_str_radix("1011101", 2).unwrap()
- Ranges can be used as iterators:
(0..5).map(|x| 2 * x - 1).collect::<Vec<_>>() - Blocks can be given labels and broken out of (see
day_04) - Variables can be declared without type and value if they're not going to be read before first assignment:
let a; a = 3 * something;
- Large
Vecinitialization using a macro:vec![vec![0, 1000]; 1000] - Argument destructuring in closures:
(&lines).into_iter().filter(|(a,b,c,d)| a == c || b == d) - Negative numbers can't easily be added to
usize:if dx * dy > 0 { y += 1; } else { y -= 1; }
- Difference between
into_iter,iteranditer_mut
- Remove whitespace around String:
trim - Custom max function on an iterator:
max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap()) - Iterate over elements in nested iterators:
flat_map - Shorten
mapandfilter map:it.map(f: x -> Result<y, _>).filter_map(Result::ok)→it.flat_map(f)Resultis an iterator that yields one value ifOk(x)and no values ifErr(e)
- Set operations using
HashSet:intersection, equality sortvs.sort_unstableiter.position(x)vec.swap(i, j)iter1.zip(iter2)
- Parse
charinto its digit value:c.to_digit(basis).unwrap() - Closures cannot be called recursively
- Only consider first
kelements of an iterator:iter.take(k)
- Extend existing
vecwith and iterator:v.extend(iter) - Labels let you break out of nested loops:
'outer: for i in ... { ... break 'outer; ... }
- Anonymous values can be used in function calls that need a reference:
f(&mut HashMap::new()) - String slices can be converted into owned strings directly:
"foo".to_owned()
- Closure parameters can't be easily given lifetimes
- Hash maps/sets can take
&strs as keys Strings can be constructed fromchararrays:String::from_iter([key.chars().nth(0).unwrap(), *c])- also works by collecting:
vec_of_chars.into_iter().collect::<String>()
- also works by collecting:
- Iterating over a changing container:
while let Some(x) = container.pop() { ... } - Multiple elements for a
Veccannot be borrowed if one borrow is mutable - A queue:
let mut queue = VecDeque; queue.push_back(x); queue.pop_front()
- Closure can take ownership of values using
move:let a = 3; let c = move || a + 3; - Strings can be formatted using format strings (see
std::fmt) - Functions that would consume an iterator can instead only take elements by passing
it.by_ref():- Take 5 elements into a
vec:it.by_ref().take(5).collect::<Vec<_>>()
- Take 5 elements into a
- Type trait's associated types can be further bounded:
fn evaluate<I: Iterator<Item = u8>>(...) ...ž
- Rust doesn't have named tuples, structs must be used
- Binary operators can be implemented for custom types by implementing traits in
std::ops
- Garbage collection can be emulated using a reference-counting pointer:
std::rc::Rc - Structs/Enums can't have nested types defined inside
- Enum variants can be brought into scope:
enum Foo { A(i32), B, ... }; use Foo::*; - Debug strings can be derived or implemented by hand for new types
- Dereferenced values can be borrowed:
let (l, r) = &**p; - Functions can be implemented to extract enum variants directly:
impl Foo { fn a(self) -> i32 { if let Foo::A(n) = self { n } else { panic!("Foo not A") } } } - Ranges can be exclusive (
a..b) or inclusive (a..=b) at the end
- If the size is known in advance:
let a: [i32; 3] = vec.as_slice().try_into().unwrap(); - Destructuring assignments:
let x, y, z; let a = [1, 2, 3]; [x, y, z] = a;- At the time of writing an unstable features, to be added in Rust 1.59.0
vecmacro can be used to initialize large vecs:let a = vec![0; 1024];- Useful iterator functions:
std::iter::once(x)returnsxoncestd::iter::repeat(x)returnsxforeverit1.chain(it2)first returns all values fromit1and then all values fromit2it1.zip(it2)returns an iterator returning tuples(x1, x2)while both iterators return valuesit.cycle()repeatedly returns all values fromit, repeating from the beginningit.enumerate()returns enumerated elements fromitit.flatten()returns elements of elements ofitit.fold(init, f)reducesitbyacc = init; loop { acc = f(acc, it.next()) }it.intersperse(x)alternates between elements ofitand returningxit.{max,min}_by(f)appliesfto each element and returns the extremeit.nth(n)discardsn - 1elements and returnsnthit.reduce(f)reduces consecutive elements byx = f(x, y)it.skip(n)returns an iterator with elements ofitfromnonwardit.take(n)returns nextnvalues fromit(consuming it in the process)it.unzip()collects tuples into a tuple of containers
- Generic
implfor genericstruct:struct Foo<T> { ... }; impl<T> Foo<T> { ... }
- Tuple-like structs:
struct Foo(i32, i32, i32); - Flush
stdout:use std::io::{self, Write}; io::stdout().flush().unwrap();
- Sometimes it's worth trying solving the problem by hand.