Mull It Over
I did today's Advent of Code puzzle (spoiler alert: if you haven't done the 2024-12-03 puzzle yet, stop reading now!) in Rust.
use std::{env, fs};
fn main() {
let filename = env::args_os().nth(1).unwrap_or("../example".into());
let contents = fs::read_to_string(filename).expect("Cannot read file!");
println!("Part1: {}", sum_muls(&contents));
println!("Part2: {}", sum_muls(&extract_dos(&contents)));
}
fn sum_muls(s: &str) -> i32 {
s.split("mul(")
.filter_map(|segment| segment.split_once(")"))
.filter_map(|pair| pair.0.split_once(","))
.filter_map(|pair|
match (pair.0.parse::<i32>(), pair.1.parse::<i32>()) {
(Ok(a), Ok(b)) => Some((a, b)),
_ => None,
},
)
.map(|pair| pair.0 * pair.1)
.sum()
}
fn extract_dos(s: &str) -> String {
let mut s = s;
let mut enabled = true;
let mut dos = String::new();
while let Some((left, right)) = do_dont(s, enabled) {
if enabled {
dos += left;
}
s = right;
enabled = !enabled;
}
if enabled {
dos += s;
}
dos
}
fn do_dont(s: &str, enabled: bool) -> Option<(&str, &str)> {
if enabled {
s.split_once("don't()")
} else {
s.split_once("do()")
}
}
After finishing, I realized I hadn't used any regular expressions. I found that odd. It seems like such a regular-expressiony problem. So I redid it in Perl (Rust does have regular expression libraries available, but they are already built in to Perl).
#!/usr/bin/env perl
use v5.40;
use Path::Tiny;
my $filename = shift @ARGV // 'example';
my $contents = path($filename)->slurp;
my $part1 = sum_muls($contents);
say "Part 1: $part1";
my $part2 = sum_muls(extract_dos($contents));
say "Part 2: $part2";
sub sum_muls ($s) {
my $sum = 0;
while ($s =~ /mul\((\d+),(\d+)\)/g) {
$sum += $1 * $2;
}
return $sum;
}
sub extract_dos($s) {
$s =~ s/don't\(\).*?do\(\)//gs;
return $s;
}
I think this is a huge improvement with regular expressions.
Some folks love to beat up on regular expressions. Both Perl and regular expressions are often sneeringly referred to as "write-only" languages, but I've found many ad hoc solutions that go out of their way to avoid regular expressions far worse.
I won't claim the code above is the best way to solve this puzzle in either language, but I think the regular expression code is easier to read.
Update: I just learned that the amazing Abigail solved the entire puzzle (both halves) with a single pass of a regular expression!
/ (?{ $factor = 1; $solution_1 = 0; $solution_2 = 0; })
(?:
\Qdo()\E (?{ $factor = 1 })
| \Qdon't()\E (?{ $factor = 0 })
| mul\(([0-9]+),([0-9]+)\) (?{ $solution_1 += $1 * $2;
$solution_2 += $1 * $2 * $factor })
| [^dm]+
| [dm] )* /x;