Linen Layout

Today's Advent of Code was so fun, I couldn't stop re-implementing it! (spoiler alert: if you haven't done the 2024-12-19 puzzle yet, stop reading now!) Here it is in Perl

#!/usr/bin/env perl

use v5.40;
use List::Util qw(sum);
use Path::Tiny;

my $filename = shift @ARGV // 'example';
my $contents = path($filename)->slurp;

my @paragraphs = split /\n\n/, $contents;

my @towels = split /, /, $paragraphs[0];
my @designs = split ' ', $paragraphs[1];

my @ways = map { ways($_, @towels) } @designs;

my $part1 = grep { $_ > 0 } @ways;
say $part1;

my $part2 = sum @ways;
say $part2;

sub ways($design, @towels) {
    my @v = (0) x length $design;
    push @v, 1;

    for (my $i = length $design; $i >= 0; $i--) {
        for my $towel (@towels) {
            my $tail = substr $design, $i;
            $v[$i] += $v[$i + length $towel]
                if $tail =~ /^$towel/;
        }
    }

    return $v[0];
}

Python

#!/usr/bin/env python

import sys


def ways(design, towels):
    v = [0] * len(design)
    v.append(1)
    for i in reversed(range(len(design))):
        for towel in towels:
            if design[i:].startswith(towel):
                v[i] += v[i + len(towel)]
    return v[0]


filename = sys.argv.pop(1) if len(sys.argv) > 1 else "example"

with open(filename) as f:
    contents = f.read()

paragraphs = contents.split("\n\n")

towels = paragraphs[0].split(", ")
designs = paragraphs[1].splitlines()

w = [ways(design, towels) for design in designs]

part1 = len([x for x in w if x > 0])
print(part1)

part2 = sum(w)
print(part2)

Ruby

#!/usr/bin/env ruby

def ways(design, towels)
  v = Array.new(design.length, 0)
  v.push(1)

  for i in design.length.downto(0)
    towels.each {|towel|
      v[i] += v[i + towel.length] if design[i..].match?(/^#{towel}/)
    }
  end

  v[0]
end

filename = ARGV.shift || 'example'
contents = File.read(filename)

paragraphs = contents.split("\n\n")

towels = paragraphs[0].split(", ")
designs = paragraphs[1].lines.map(&:chomp)

w = designs.map {|design| ways(design, towels) }

part1 = w.select {|x| x > 0 }.length
puts part1

part2 = w.sum 
puts part2

and Rust

use std::{env, fs};

fn main() {
    let filename = env::args().nth(1).unwrap_or("../example".into());
    let contents = fs::read_to_string(&filename).expect("Cannot read file!");

    let (towels, designs) = contents.split_once("\n\n").unwrap();
    let towels = towels.split(", ").collect::<Vec<_>>();

    let w = designs
        .lines()
        .map(|design| ways(design, &towels))
        .collect::<Vec<_>>();

    let part1 = w.iter().filter(|&&ways| ways > 0).count();
    println!("Part 1: {part1}");

    let part2 = w.iter().sum::<usize>();
    println!("Part 2: {part2}");
}

fn ways(design: &str, towels: &[&str]) -> usize {
    let mut v = vec![0; design.len() + 1];
    v[design.len()] = 1;
    for i in (0..design.len()).rev() {
        for &towel in towels {
            if design[i..].starts_with(towel) {
                v[i] += v[i + towel.len()];
            }
        }
    }
    v[0]
}

I noticed that ruff put extra whitespace before and after the subroutine definition in Python (I have Emacs configured to use ruff when I invoke eglot-format). That's nice! Python makes us define the subroutine before invoking it, which is annoying (I prefer it at the end, as in Perl and Rust), but the extra whitespace kind of takes the edge off.

Python code in Emacs formatted with ruff

I think I prefer the "suffix if" in Perl

            $v[$i] += $v[$i + length $towel]
                if $tail =~ /^$towel/;

and Ruby

      v[i] += v[i + towel.length] if design[i..].match?(/^#{towel}/)

to the old-fashioned "if" in Python

            if design[i:].startswith(towel):
                v[i] += v[i + len(towel)]

and Rust

            if design[i..].starts_with(towel) {
                v[i] += v[i + towel.len()];
            }

I think Ruby's downto is the most elegant of the reversed loops.

  for i in design.length.downto(0)

Perl's C-style three-part for-loop is blecherous

    for (my $i = length $design; $i >= 0; $i--) {

I often claim I rarely use it in Perl, but honestly it seems like the least awful way to do this. Python's reversed

    for i in reversed(range(len(design))):

and Rust's rev

    for i in (0..design.len()).rev() {

are not so bad, but Ruby is downright delightful!

I love how Rust— the low-level "systems" language— looks every bit as concise and beautiful as the fancy scripting languages. I will always love Perl, Python, and Ruby, but Rust is growing on me the more I use it. And I think I might be getting as fast— or even faster— developing in Rust too.