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;