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.

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.