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.