Advent of Code: Day 1 & 2
After a couple of months since completing the minigrep
program in Rust from the Rust Book, I wanted to get my hands dirty. I tried to rawdog writing a compiler to, shockingly, absolutely no success. With Advent of Code, I thought it’s a good excuse to practice my rusty Rust and refresh my super basic Rust skills.
Day 1
Part 1
The solution itself was quite simple, but it took me around 2 hours just to get it working, because I forgot all the syntax and data types and what not. This is my first draft:
use std::fs;
fn main() {
// list 1 and list 2
// pair up the numbers in ascending order, and find the distance between them
// then, add all the numbers
// 1. load input (txt?)
// 2. loop through, assign left, then right; list1, then list2
// 3. sort line1 and line2
// 4. compare per index, total each diff into a tmp var
// 5. return tmp var
let input = fs::read_to_string("input.txt").unwrap();
let mut line1: Vec<i32> = vec![];
let mut line2: Vec<i32> = vec![];
for line in input.lines() {
let tmp: Vec<&str> = line.split(" ").collect();
line1.push(tmp[0].parse().unwrap());
line2.push(tmp[1].parse().unwrap());
}
line1.sort();
line2.sort();
let mut result: i32 = 0;
for (i, x) in line1.iter().enumerate() {
let line2 = &line2[i];
if x >= line2 {
result += x - line2;
} else {
result += line2 - x;
}
}
println!("{}", result);
}
And it worked! I did not care much about error handling at the time; my primary focus was to get a correct, working code. There are a couple of things I realised I can do better afterwards, such as replacing .split(" ")
to .split_whitespace()
. Another is using result += x.abs_diff(line2)
instead of the cursed if else
statement to not get a negative number.
Part 2
The only difference in part 2 is during the looping through. Add another loop to “collect” multipliers, and adding multiplier * x
to the result
// snip
let mut result: i32 = 0;
for x in line1.iter() {
// loop through line2 until no more of same number
let mut multiplier: i32 = 0;
for (j, y) in line2.iter().enumerate() {
if x == y {
multiplier += 1;
}
// end of loop
if j == line2.len() - 1 {
result += multiplier * x;
multiplier = 0;
}
}
}
// snip
Day 2
On Day 2, I wanted to mess with proper error handling (at least more thought out than in Day 1). I also wanted to measure how long the execution time is. Luckily, we can do this quite easily with Rust!
let start = Instance::now();
// code to be executed here...
let duration = start.elapsed();
println!("{}", duration);
Part 1
For Day 2, I wanted to keep both solutions in one cargo
project, compared to two when I was doing Day 1. This way, I only need to write the logic for reading the input data once. I also wanted to type cargo run <input file>
to be able to run between the test data and real input data.
use core::panic;
use std::{env, fs, process, time::Instant};
fn main() {
// input is one report per line
// report = line (y-value)
// levels = columns (x-value)
// count how many reports are safe!
// safe can be based on:
// 1. each report must be constantly increasing/decreasing (can't look like /\)
// 2. delta is 1 <= d <= 3
// 3. if delta == 0, it's unsafe
let start = Instant::now();
let filename: Vec<String> = env::args().collect();
if filename.len() < 2 {
eprintln!("Error: no filename detected. Please enter filename.");
process::exit(1);
}
let input = match fs::read_to_string(&filename[1]) {
Ok(string) => string,
Err(_) => {
panic!("Can't read file. Please make sure file exists.")
}
};
// let res = part1(input);
let res = part2(input);
println!("total: {}", res);
let duration = start.elapsed();
println!("time elapsed: {:?}", duration);
}
I know, I know.. I still have to change the code manually to run day 1 vs day 2 code. This could be extended in Day 3 perhaps - cargo run <input> <day>
For Part 1, the solution was again, quite straightforward, though longer than Day 1. This forced me (especially due to part 2) to break up my code into functions - like how it should be!
pub fn part1(input: String) -> i32 {
let mut total_safe = 0;
for line in input.lines() {
let row: Vec<&str> = line.split_whitespace().collect();
let res = check_if_safe(&row);
if res {
total_safe += 1;
}
}
return total_safe;
}
pub fn check_if_safe(row: &Vec<&str>) -> bool {
let mut delta_is_positive = true;
let mut safe = true;
for (j, x) in row.iter().enumerate() {
// logic starts after index 0
if j > 0 {
let cur_value = x.parse::<i32>().unwrap();
let prev_value = row[j - 1].parse::<i32>().unwrap();
let delta = cur_value - prev_value;
let delta_diff = delta.abs_diff(0);
if delta_diff < 1 || delta_diff > 3 {
safe = false;
break;
}
if delta == 0 {
safe = false;
break;
} else if delta > 0 {
if j > 1 {
if delta_is_positive == false {
safe = false;
break;
}
}
delta_is_positive = true;
} else {
if j > 1 {
if delta_is_positive == true {
safe = false;
break;
}
}
delta_is_positive = false;
}
}
}
return safe;
}
Upon copy/pasting the code to Notion right now, I just realised that I could probably remove the safe
variable in check_if_safe
function and replacing the break
with an early return false
, returning true
by default if no early returns are called. +1 learning!
Writing the solution in Day 2 also forced me to think more about passing by reference or by value to functions. The compiler throws a warning/error if I tried to access a value that has been moved/borrowed, which was really helpful to write proper Rust code. Luckily, I have not need to think about lifetimes yet - the time will come.
Part 2
I thought Part 2 is quite tricky, because I couldn’t figure it out on my own the first try. My first solution was a crazy amount of nested if
statements - I got lost in the sauce. Not only was I lost in the sauce, my solution was outright wrong. I had to find an implementation in JavaScript, digest it, and implemented it in Rust by myself.
pub fn part2(input: String) -> i32 {
let mut total_safe = 0;
for line in input.lines() {
let row: Vec<&str> = line.split_whitespace().collect();
// each row, generate all possibilities
// then, check if it's safe or not
if check_if_safe(&row) {
total_safe += 1;
} else {
for i in 0..row.len() {
let mut modified_row: Vec<&str> = Vec::clone(&row);
modified_row.remove(i);
if check_if_safe(&modified_row) {
total_safe += 1;
break;
};
}
}
}
return total_safe;
}
Initial Thoughts
After the first 2 days of Advent of Code, I think that it’s a super good tool to learn a new language! Especially if you’re starting out, since the problems (until now) are not super complicated, hence more time can be spent on figuring out the syntax of the language itself. As the days go on and the challenges harder, the learning curve would probably get steeper, but I’m looking forward to do it!