2024 AoC Day 1 – Historian Hysteria

This is a solution to Advent of Code 2024 day 1, written in Raku and in Rust.

https://adventofcode.com/2024/day/1

Part One

To find the total distance between the left list and the right list, add up the distances between all of the pairs you found. In the example above, this is 2 + 1 + 0 + 1 + 2 + 5, a total distance of 11!

Your actual left and right lists contain many location IDs. What is the total distance between your lists?

Raku

Half of the challenge is taking the input which has two values per line and pivoting it to group by column instead. Then it is sorted before a zip merge and reduction to a sum of differences.

my @pairs = 'input-1.txt'.IO.lines>>.split(/\s+/)>>.Int;

my @a = @pairs.map(-> ($a, $b) { $a }).sort;
my @b = @pairs.map(-> ($a, $b) { $b }).sort;

say [+] (@a Z @b).map(-> ($a, $b) { abs($a - $b) });
1580061

Rust

Here is part 1 implemented in Rust using the same approach as the Raku code, albeit with a lot more saying exactly what I mean. Not sure if this is idomatic Rust, or even half-sane Rust, but there we go. A big surprise for me is that sort() can't be used in a fluent style.

use std::error::Error;
use std::fs;

fn main() -> Result<(), Box<dyn Error>> {
    let input = fs::read_to_string("/Users/donaldh/git/advent-of-code/2024/input-1.txt")?;
    let pairs: Vec<Vec<i32>> = input.lines()
        .map(|x| x.split("   ").map(|x| x.parse::<i32>().unwrap()).collect())
        .collect();

    let mut left: Vec<i32> = pairs.iter().map(|x| x[0]).collect();
    left.sort();
    let mut right: Vec<i32> = pairs.iter().map(|x| x[1]).collect();
    right.sort();

    let distances: Vec<i32> = left.iter().zip(right.iter())
        .map(|(a, b)| { (a - b).abs() })
        .collect();
    let total: i32 = distances.iter().sum();
    println!("{:#?}", total);

    Ok(())
}
1580061

Part Two

This time, you'll need to figure out exactly how often each number from the left list appears in the right list. Calculate a total similarity score by adding up each number in the left list after multiplying it by the number of times that number appears in the right list.

Once again consider your left and right lists. What is their similarity score?

Raku

Roughly the same first step, grouping the data by column, but then converting the second column to a Bag so we can efficiently look up the occurences for each item in the first column.

my @pairs = 'input-1.txt'.IO.lines>>.split(/\s+/)>>.Int;

my @a = @pairs.map(-> ($a, $b) { $a });
my $b = @pairs.map(-> ($a, $b) { $b }).Bag;

say [+] @a.map(-> $n { $n * $b{$n} // 0 });
23046913

Rust

There isn't a bag data structure in the Rust std crate and I'm not going to reach for other crates so I need to roll my own. It's surprisingly easy to fill a map with counted occurrences. Again I don't know if this is idiomatic.

use std::error::Error;
use std::fs;
use std::collections::BTreeMap;

fn main() -> Result<(), Box<dyn Error>> {
    let input = fs::read_to_string("/Users/donaldh/git/advent-of-code/2024/input-1.txt")?;
    let pairs: Vec<Vec<i32>> = input.lines()
        .map(|x| x.split("   ").map(|x| x.parse::<i32>().unwrap()).collect())
        .collect();

    let left: Vec<i32> = pairs.iter().map(|x| x[0]).collect();
    let right: Vec<i32> = pairs.iter().map(|x| x[1]).collect();

    let mut right_bag = BTreeMap::new();
    for value in right.iter() {
        if let Some(n) = right_bag.get_mut(value) {
            *n += 1;
        } else {
            right_bag.insert(value, 1);
        }
    }

    let mut total: i32 = 0;
    for value in left.iter() {
        if let Some(n) = right_bag.get(value) {
            total += value * n;
        }
    }
    println!("{:#?}", total);

    Ok(())
}
23046913
raku