Advent of Code
Today's Advent of Code was so fun, I did it twice!
Warning: Spoilers ahead! Turn back if you haven't done today's puzzle yet.
First, I did it in Rust. I've been doing every day in Rust this year, so today started out no different.
use std::{env, fs};
fn main() {
let arguments = env::args().collect::<Vec<_>>();
let filename = if arguments.len() > 1 {
&arguments[1]
} else {
"../example"
};
let contents = fs::read_to_string(filename).expect("Cannot read file!");
let steps = contents.trim_end().split(',').collect::<Vec<_>>();
let part1 = steps.iter().map(|&step| hash(step)).sum::<usize>();
println!("Part 1: {part1}");
let part2 = focusing_power(&hashmap(&steps));
println!("Part 2: {part2}");
}
fn find_label(b: &Vec<(&str, usize)>, label: &str) -> Option<usize> {
for (i, (l, _)) in b.iter().enumerate() {
if *l == label {
return Some(i);
}
}
None
}
fn focusing_power(boxes: &[Vec<(&str, usize)>]) -> usize {
let mut fp = 0;
for (i, b) in boxes.iter().enumerate() {
if !b.is_empty() {
for (j, (_, f)) in b.iter().enumerate() {
fp += (i + 1) * (j + 1) * f;
}
}
}
fp
}
fn hash(s: &str) -> usize {
let mut h = 0;
for c in s.chars() {
let a: u32 = c.into();
h = ((h + a) * 17) % 256;
}
h as usize
}
fn hashmap<'a>(steps: &'a [&str]) -> Vec<Vec<(&'a str, usize)>> {
let mut boxes = vec![Vec::new(); 256];
for step in steps {
let eq = step.split('=').collect::<Vec<_>>();
if eq.len() > 1 {
let label = eq[0];
let box_num = hash(label);
let focal_length = eq[1].parse::<usize>().unwrap();
if let Some(index) = find_label(&boxes[box_num], label) {
if let Some(lens) = boxes[box_num].get_mut(index) {
*lens = (label, focal_length);
}
} else {
boxes[box_num].push((label, focal_length));
}
} else {
let label = step.trim_end_matches('-');
let box_num = hash(label);
if let Some(index) = find_label(&boxes[box_num], label) {
boxes[box_num].remove(index);
}
}
}
boxes
}
It was quick and easy. And really fun! Then I decided to try it in Perl. I've done previous years of Advent of Code in Perl, but I hadn't done any this year yet. It just seemed to me that Perl's autovivification and array splice would really make short work of this.
#!/usr/bin/env perl
use v5.38;
use List::Util qw(reduce sum);
my $file = shift // '../example';
open my $fh, '<', $file or die "Cannot open '$file': $!";
my $line = readline $fh;
chomp $line;
my @steps = split ',', $line;
my $part1 = sum map {hash($_)} @steps;
say "Part 1: $part1";
my $part2 = focusing_power(hashmap(@steps));
say "Part 2: $part2";
sub find_label($label, @box) {
for my $i (0..$#box) {
if ($label eq $box[$i][0]) {
return $i;
}
}
return;
}
sub focusing_power(@boxes) {
my $fp = 0;
for my $i (0..$#boxes) {
if (defined $boxes[$i]) {
for my $j (0..$#{$boxes[$i]}) {
$fp += ($i + 1) * ($j + 1) * $boxes[$i][$j][1];
}
}
}
$fp
}
sub hash($s) {
reduce {(($a + ord $b) * 17) % 256} 0, split '', $s
}
sub hashmap(@steps) {
my @boxes;
for my $step (@steps) {
if ($step =~ /^(?<label>[^=]+)=(?<focal_length>\d+)$/) {
my $lens = [$+{label}, $+{focal_length}];
my $box = hash($+{label});
my $index = find_label($+{label}, @{$boxes[$box]});
if (defined $index) {
splice @{$boxes[$box]}, $index, 1, $lens;
} else {
push @{$boxes[$box]}, $lens;
}
}
if ($step =~ /^(?<label>[^-]+)-$/) {
my $box = hash($+{label});
my $index = find_label($+{label}, @{$boxes[$box]});
if (defined $index) {
splice @{$boxes[$box]}, $index, 1;
}
}
}
@boxes
}
I wasn't wrong (I love just pushing things onto an array that doesn't exist yet), but I think it's interesting how similar the two solutions look. I mean, Rust is supposedly a "low level" systems language, while Perl is supposedly a "scripting" language. Granted, this is a pretty easy problem and both solutions were written by the same programmer, but I'm still pleased with the affordances of both languages.
I mention this a little bit in From Perl to Rust, but it's nice to see it in action. These two read pretty much the same to me.