Advent of Code 2020
This is my notes about solving Advent of Code 2020 in Rust.
Day 1
- There was outage for the first 10 minutes so there were no leaderboard scores
 - I solved this using 3 nested loops:
 
for i in 0..x.len() {
    for j in 0..i {
        for k in 0..j {
            if x[i] + x[j] + x[k] == 2020 {
                return x[i] * x[j] * x[k];
            }
        }
    }
}
- Afterwards learned about 
combinationsfunction fromitertoolscrate: 
let v = x
    .iter()
    .cloned()
    .combinations(3)
    .filter(|it| it.iter().sum::<i64>() == 2020)
    .next()
    .unwrap();
return v[0] * v[1] * v[2]
- Using 
tuple_combinationsfrom the same package looks even cleaner: 
let res = x
    .iter()
    .cloned()
    .tuple_combinations()
    .find_map(|(a, b, c)| {
        if a + b + c == 2020 {
            Some(a * b * c)
        } else {
            None
        }
    })
    .unwrap();
return res
Day 2
- Solved in 5/9 minutes and got around 800th place overall
 

- Wasted some time figuring out how to index 
String-s in Rust. The syntax is pretty ugly: 
let ch = parts[1].chars().nth(0).unwrap();
- Lost another minute or so with incorrect answers as missed 1-indexing of arrays
 - Initially did some parsing using my 
split_stringandparse_i64functions: 
let parts = split_string(line, " ");
let nums = split_string(&parts[0], "-");
let a = parse_i64(&nums[0]);
let b = parse_i64(&nums[1]);
let ch = parts[1].chars().nth(0).unwrap();
let pass = parts[2];
- Afterwards found some pretty slick 
serde_scancrate: 
let (a, b, ch, pass): (usize, usize, char, String) =
    serde_scan::scan!("{}-{} {}: {}" <- line).unwrap();
Day 3
- Solved in 
6:44/10:11minutes and got at#1240/#953in global leaderboard 

- Screwed up several times while trying to skim the statement too fast:
    
- Missed that starting point is (1, 3) and not (0, 0) - this probably doesn’t matter since the start is not at tree
 - Didn’t realize that the map is cyclical
 - Missed that we need to multiply all numbers instead of sum
 
 - Regarding Rust itself - I don’t like the access pattern for strings, so maybe I should have a helper to quickly convert 
StringtoVec<Char>: 
pub fn to_vv_char(lines: &Vec<String>) -> Vec<Vec<char>> {
    lines.iter().map(|x| x.chars().collect()).collect()
}
Day 4
- Solved in 
11:19/28:05minutes and got at#1800/1091in global leaderboard 

- Didn’t really like the problem since it was pretty straightforward but required a lot of careful coding
 - Maybe that’s an opportunity to learn how to do that more efficiently in Rust (e.g. use regexps)
 - For first part missed that 
cidis optional - Also wasted some time with incorrect parsing - didn’t take into account last block
 - Surprisingly second part worked out of the box the first time I finished it
 - Will check if I can simplify some of the code
 - Found that 
Vechas methodcontainsand splitting the string is probably faster than changing it to a vector, soeclcheck could be written as following: 
fn check_ecl(s: &String) -> bool {
    let opts = split_string(&"amb blu brn gry grn hzl oth".to_string(), " ");
    opts.contains(s)
}
- iterators have methd 
.allthat could be used for simplification: 
fn is_all_digits(s: &String) -> bool {
    s.chars().all(|c| c.is_numeric())
}
Day 5
- Solved in 
7:07/8:54minutes and got at#593/379place. The best result so far! 

- Was pretty straightforward problem - I feel that I didn’t really screwed up anywhere
 - Initially I implemented the binary search as described, but later turned out you can just convert the input string to binary treating “BR” -> 1 and “FL” -> 0:
 
fn get_seat_id_bin(s: &String) -> i64 {
    let x: String = s
        .chars()
        .map(|c| match c {
            'B' | 'R' => '1',
            _ => '0',
        })
        .collect();
    isize::from_str_radix(&x, 2).unwrap() as i64
}
- Or using 
.replaceinstead: 
fn get_seat_id_bin(s: &String) -> i64 {
    let x = s.replace("B", "1").replace("R", "1").replace("L", "0").replace("F", "0");
    isize::from_str_radix(&x, 2).unwrap() as i64
}
Day 6
- Solved in 
6:31/8:59and got at#1911/#839: was a little slow than people in part 1 but then got faster in part 2. 

- The parsing was pretty similar to day 4 and I still was using awkward state based parsing which is slower to implement
 - Would be interesting to switch to 
.split("\n\n")based parsing since this seems more effective - Part 1 using this approach:
 
pub fn part1(data: &str) -> i64 {
    data.trim()
        .split("\n\n")
        .map(|x| x.replace("\n", ""))
        .map(|x| {
            let set: HashSet<_> = x.chars().collect();
            set.len() as i64
        })
        .sum()
}
- Interestingly, Rust now only has 
.fold(value, func)which accumulates results into initial value. There is.fold_first(func)method in nightly that is more similar to traditionalreduce(it uses the first value as initial). - There is alo discussion on rust-lang API for supporting multiple sets
 - Without that writing part 2 is a little awkward:
 
pub fn part2(data: &str) -> i64 {
    data.trim()
        .split("\n\n")
        .map(|x| x.split("\n"))
        .map(|lines| {
            let sets: Vec<_> = lines
                .map(|x| {
                    let set: HashSet<_> = x.chars().collect();
                    set
                })
                .collect();
            sets.iter()
                .fold(sets[0].clone(), |acc, s| {
                    acc.intersection(&s).copied().collect()
                })
                .len() as i64
        })
        .sum()
}
Day 7
- Solved in 
21:27/30:08and got at#1305/#1006. 

- Spent some time trying to make fancy serde_scan work, but didn’t succeed and swtiched back to regular parsing/splitting
 - Solved part 1 using BFS and part 2 using DFS with caching
 - Learned cool API in Rust to populate 
HashMap<_, Vec<_>>. In C++ this is easy because of default ctor-s, but in Rust the convenient way to use the following: 
g.entry(input_bag.clone())
    .or_insert(Vec::new())
    .push((num, output_bag.clone()));
- Parsing could also be re-written using 
regexcrate: 
let re = Regex::new("(.*) contain (.*)[.]").unwrap();
let cap = re.captures(line).unwrap();
let src = cap.get(1).unwrap().as_str();
let dsts = cap.get(2).unwrap().as_str();
Day 8
- Solved in 
10:40/16:47and got at#2519/#1144. 

- Pretty slow overall as I spent debugging a few bugs in code:
    
- Missed 
ipincrement on regular instructions - Overflow of jump backwards instruction
 
 - Missed 
 - Learned that using pattern matching can make the program nicer:
 
fn run_prog(prog: &Vec<Op>) -> (bool, i64) {
    let mut acc = 0;
    let mut ip = 0;
    let mut visited = HashMap::new();
    loop {
        if ip as usize >= prog.len() {
            return (true, acc);
        }
        let key = ip;
        if visited.contains_key(&key) {
            return (false, acc);
        }
        visited.insert(key, true);
        let p = &prog[ip as usize];
        match p.name.as_str() {
            "acc" => {
                acc += p.value;
                ip += 1;
            }
            "jmp" => {
                ip = (ip + p.value).max(0);
            }
            _ => {
                ip += 1;
            }
        }
    }
}
Day 9
      -------Part 1--------   -------Part 2--------
Day       Time  Rank  Score       Time  Rank  Score
  9   00:07:27  1224      0   00:13:01   887      0
- Pretty easy day again. Mostly fighting my own typos / mistakes.
 - Learned a few cool things though.
 - Rust has convenient lambdas defined inline that could capture values from outside:
 
let is_valid = |x, start, end| {
    for i in start..end {
        for j in start..i {
            if v[i] + v[j] == x {
                return true;
            }
        }
    }
    return false;
};
- Rust ranges work on vectors:
 
let s: i64 = v[i..=j].iter().sum();
let min = v[i..=j].iter().min().unwrap();
let max = v[i..=j].iter().max().unwrap();
Day 10
      -------Part 1--------   -------Part 2--------
Day       Time  Rank  Score       Time  Rank  Score
 10   00:06:01   559      0   00:14:10   358      0
- Did pretty good - was pretty straightforward dynamic programming and worked almost from the first run
 
Day 11
      -------Part 1--------   -------Part 2--------
Day       Time  Rank  Score       Time  Rank  Score
 11   00:13:00   374      0   00:17:56   209      0
- Did pretty well - this is the best day so far.
 - Solved Part 2 with 8 (!) nested scopes
 - Later learned that rust allows overloading of any methods from existing struct. Here is how I was implementing 
.getand.setmethods for vector based on a Vector2d (which is basically (x, y) pair): 
pub trait GridExt {
    fn get(&self, v: Vector2d) -> char;
    fn set(&mut self, v: Vector2d, c: char);
}
impl GridExt for Vec<Vec<char>> {
    fn get(&self, v: Vector2d) -> char {
        self[v.y as usize][v.x as usize]
    }
    fn set(&mut self, v: Vector2d, c: char) {
        self[v.y as usize][v.x as usize] = c;
    }
}
Day 12
      -------Part 1--------   -------Part 2--------
Day       Time  Rank  Score       Time  Rank  Score
 12   00:10:22   871      0   00:15:54   356      0
- Had decent part 1, but then speed up at part 2 and gained 500 ranks.
 - Learned that 
matchis pretty convenient to use in Rust. You can also skip brackets{ ... }if there just single expression inside match arm. 
for (d, v) in actions {
    match d {
        'N' => wpt.y += v,
        'S' => wpt.y -= v,
        'E' => wpt.x += v,
        'W' => wpt.x -= v,
        'L' => {
            for i in 0..v / 90 {
                wpt = wpt.rotate_left();
            }
        }
        'R' => {
            for i in 0..v / 90 {
                wpt = wpt.rotate_right();
            }
        }
        'F' => pos = pos + wpt * v,
        _ => {}
    }
}
Day 13
      -------Part 1--------   -------Part 2--------
Day       Time  Rank  Score       Time  Rank  Score
 13   00:03:58    61     40   01:21:36  2161      0
- Did part 1 really was to even get into leaderboard, but then struggled with Part 2 as I forgot about Chineese Remainder Theorem (CRT)
 - Some folks also solved it using sieving method that should be fast to implement.
 - I essentially came up with inductive formula based on diophantine equation solver 
a * x + b * y = c - However I also tried Rust’s big integers and found they are clunky as hell as require tons of 
.clone()calls all over the place. Worth checking if there are better alternatives. 
Day 14
      -------Part 1--------   -------Part 2--------
Day       Time  Rank  Score       Time  Rank  Score
 14   00:18:35  1308      0   00:31:07   679      0
- Had a bug with bitwise operations in part 1 which caused timings quite slow
 
Day 15
      -------Part 1--------   -------Part 2--------
Day       Time  Rank  Score       Time  Rank  Score
 15   00:19:18  1952      0   00:38:07  2573      0
- Was solving part 1 with O(N^2) alogrithm and had many bugs in the process
 
Day 16
      -------Part 1--------   -------Part 2--------
Day       Time  Rank  Score       Time  Rank  Score
 16   00:16:36  1433      0   00:37:44   684      0
- Solved Part 1 in naive way by iterating through all columns and filling the match if there was only 1 possible one
 
Learned:
.any()method on iterators in rust
let invalid = nums.iter().any(|x| is_outside_every(*x, &ranges));
dbg!(..)to print values:
dbg!(tickets.len());
// [src/bin/day16.rs:98] tickets.len() = 191
Day 24
- Learned cool syntax for map update:
 
*tiles.entry(key).or_insert(0) += 1;
- Variables to functions could have 
mutmodifier 
fn walk(mut line: &str) -> (i64, i64) {
    while !line.is_empty() {
        line = line[1..]
    }
}
Final thoughts

- This year I continued using Rust. Now feeling more profficient - can probably solve most of the puzzles without looking into reference
 
Links
- Other Rust Solutions:
 - Vizualization of time taken for top-100:
 - Collection of many other related links: