December 2024 / Rust

Solving Advent of Code 2024 in Rust!

It’s that time of the year again. Picking up a new language and solving some fun problems. Here’s my solutions to Advent of Code 2024 in Rust.

Day 1

Pretty straightforward day, nothing too crazy. Mostly just getting familiar with the functional side of Rust.

use std::collections::HashMap;

// part 1
pub fn distance_between_lists(list1: &mut [i32], list2: &mut [i32]) -> i32 {
    list1.sort();
    list2.sort();
    let it = list1.iter().zip(list2.iter());
    it.map(|(l, r)| (l - r).abs()).sum()
}

// part 2
pub fn similarity_score(list1: &[i32], list2: &[i32]) -> i32 {
    let mut ans = 0;
    let mut left: HashMap<i32, i32> = HashMap::new();
    let mut right: HashMap<i32, i32> = HashMap::new();
    for (l, r) in list1.iter().zip(list2.iter()) {
        let lval = left.entry(*l).or_insert(0);
        let rval = right.entry(*r).or_insert(0);
        *lval += 1;
        *rval += 1;
    }
    for (val, count) in left {
        ans += right.get(&val).unwrap_or_else(|| &0) * val * count;
    }
    ans
}

Day 2

This one was ridiculous. I spent about a day trying to figure out how to write part 2 in a way that was similar to part 1. I really like the way I managed to do part 1. Probably not the most efficient but it’s immediately obvious what’s going on. After a couple of hours of pulling my hair out, I finally gave up and just wrote something iterative. Still nice, just not as elegant as I’d have liked.

use std::ops::RangeInclusive;

// part 1
// I really like this solution, it's probably not the most efficient but god is it pretty
pub fn safe_level(levels: Vec<Vec<i32>>) -> usize {
    levels.iter().filter(|level| {
        let is_increasing = level.windows(2).all(|pair| pair[0] < pair[1]);
        let is_decreasing = level.windows(2).all(|pair| pair[0] > pair[1]);
        let valid_diff = level.windows(2).all(|pair| {
            let diff = pair[0].abs_diff(pair[1]);
            (1..=3).contains(&diff)
        });
        (is_increasing || is_decreasing) && valid_diff
    }).count()
}

// part 2
pub fn safe_level_with_one_remove(levels: Vec<Vec<i32>>) -> usize {
    let mut count = 0;
    let increasing = 1..=3;
    let decreasing = -3..=-1;
    for level in levels {
        let res = [is_safe(&level, &increasing, true),
            is_safe(&level, &decreasing, true),
            is_safe(&level[1..].to_vec(), &increasing, false),
            is_safe(&level[1..].to_vec(), &decreasing, false)
        ];
        count += res.iter().any(|r| *r) as usize;
    }
    count
}

fn is_safe(level: &Vec<i32>, range: &RangeInclusive<i32>, mut allow_skip: bool) -> bool {
    let mut prev = level[0];
    for current in level.iter().skip(1) {
        if range.contains(&(prev - current)) {
            prev = *current;
        } else if !allow_skip {
            return false;
        }
        else {
            allow_skip = false;
        }
    }
    true
}

Day 3

Quite straightforward. Just some regex and string manipulation.

use regex::Regex;

pub fn identify_multiply(input: &str) -> u32 {
    let mul = Regex::new(r"(mul\(\d+,\d+\))|(do\(\)|don't\(\))").unwrap();
    let mut enabled = true;
    let mut result = 0;
    for (_, [capture]) in mul.captures_iter(input).map(|c| c.extract()) {
        match capture.starts_with("mul") {
            true => {
                if enabled {
                    let (num1, num2) = capture[4..7].split_once(',').unwrap();
                    result += num1.parse::<u32>().unwrap() * num2.parse::<u32>().unwrap();
                }
            }
            false => enabled = capture == "do()"
        }
    }
    result
}