Advent of Code 2024: Day 3

> zig version
0.13.0

For this problem, I used a regular expression in my Lisp solution. Here, I went for a small Finite-State Machine.

The only interesting new piece of Zig is the use of an enum. Enums can have their own functions, so we can call state.next(char) in the loop running through the input.

const State = enum {
    start, seen_d, seen_do, seen_doo, finish_do,                    // <1>
    seen_don, seen_dona, seen_donat, seen_donato, finish_donat,
    seen_m, seen_mu, seen_mul, seen_mulo, seen_mulon, seen_mulonc, 
    seen_muloncn, finish_mul,

    pub fn next (self: State, char: u8) State {                     // <2>
        if (char == 'd') {                                          // <3>
            return .seen_d;
        } else if (char == 'm') {
            return .seen_m;
        } else if (self == .seen_d and char == 'o') {               // <4>
            return .seen_do;
        // do()
        } else if (self == .seen_do and char == '(') {
            return .seen_doo;
        } else if (self == .seen_doo and char == ')') {
            return .finish_do;
        // don't()
        } else if (self == .seen_do and char == 'n') {
            return .seen_don;
        } else if (self == .seen_don and char == '\'') {
            return .seen_dona;
        } else if (self == .seen_dona and char == 't') {
            return .seen_donat;
        } else if (self == .seen_donat and char == '(') {
            return .seen_donato;
        } else if (self == .seen_donato and char == ')') {
            return .finish_donat;
        // mul(nnn,nnn)
        } else if (self == .seen_m and char == 'u') {
            return .seen_mu;
        } else if (self == .seen_mu and char == 'l') {
            return .seen_mul;
        } else if (self == .seen_mul and char == '(') {
            return .seen_mulo;
        } else if (self == .seen_mulo and isDigit(char)) {
            return .seen_mulon;
        } else if (self == .seen_mulon and isDigit(char)) {
            return .seen_mulon;
        } else if (self == .seen_mulon and char == ',') {
            return .seen_mulonc;
        } else if (self == .seen_mulonc and isDigit(char)) {
            return .seen_muloncn;
        } else if (self == .seen_muloncn and isDigit(char)) {
            return .seen_muloncn;
        } else if (self == .seen_muloncn and char == ')') {
            return .finish_mul;
        // all other chars bring us back to start
        } else {
            return .start;
        }
    }
};
  1. Create a separate state for each meaningful step.
  2. A function is created for the state type.
  3. All character "d" steps will reset us to the seen_d state.
  4. Most checks are for a state and character, giving the next state.

The main loop runs through each character in the input in turn, picks up the next state, and performs an action in some cases.

    for (line) |char| {
        state = state.next(char);
        // check actions in specific states
        if (state == State.finish_do and ignore_does) {
            mul_enabled = true;
        } else if (state == State.finish_donat and ignore_does) {
            mul_enabled = false;
        } else if (state == State.seen_mulo) { // when starting mul, zero the args
            mul1 = 0;
            mul2 = 0;
        } else if (state == State.seen_mulon) {
            mul1 = 10 * mul1 + (char - '0');
        } else if (state == State.seen_muloncn) {
            mul2 = 10 * mul2 + (char - '0');
        } else if (state == State.finish_mul and mul_enabled) {
            total += mul1 * mul2;
        }
    }

Final Program

const std = @import("std");

pub fn main() !void {
    // get allocator
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    const allocator = arena.allocator();
    defer _ = arena.deinit();

    // get command-line args
    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    // see if filename is on command-line
    if (args.len == 2) {
        const filename = args[1];
        solveDay(allocator, filename) catch |err| {
            switch (err) {
                error.FileNotFound => {
                    std.debug.print("Error: File {s} was not found.\n", .{filename});
                    std.process.exit(1);
                },
                else => {
                    std.debug.print("Error: in processing file.\n", .{});
                    std.process.exit(1);
                },
            }
        };
    } else {
        std.debug.print("Provide a filename of input data.\n", .{});
    }
}

fn solveDay (allocator: std.mem.Allocator, filename: []const u8) !void {
    const data = try readData(allocator, filename);

    std.debug.print("Part 1: {d}\n", .{sumMuls(data, false)});
    std.debug.print("Part 2: {d}\n", .{sumMuls(data, true)});
}

fn sumMuls(line: []const u8, ignore_does: bool) usize {
    var total: usize = 0; // holds growing total, for result
    var mul1: usize = 0; // holds value of mul() first argument
    var mul2: usize = 0; // holds value of mul() second argument
    var state = State.start; // current state of system
    var mul_enabled = true; // flag to indicate if muls should be done

    for (line) |char| {
        state = state.next(char);
        // check actions in specific states
        if (state == State.finish_do and ignore_does) {
            mul_enabled = true;
        } else if (state == State.finish_donat and ignore_does) {
            mul_enabled = false;
        } else if (state == State.seen_mulo) { // when starting mul, zero the args
            mul1 = 0;
            mul2 = 0;
        } else if (state == State.seen_mulon) {
            mul1 = 10 * mul1 + (char - '0');
        } else if (state == State.seen_muloncn) {
            mul2 = 10 * mul2 + (char - '0');
        } else if (state == State.finish_mul and mul_enabled) {
            total += mul1 * mul2;
        }
    }

    return total;
}

const State = enum {
    start, seen_d, seen_do, seen_doo, finish_do,
    seen_don, seen_dona, seen_donat, seen_donato, finish_donat,
    seen_m, seen_mu, seen_mul, seen_mulo, seen_mulon, seen_mulonc, 
    seen_muloncn, finish_mul,

    pub fn next (self: State, char: u8) State {
        if (char == 'd') {
            return .seen_d;
        } else if (char == 'm') {
            return .seen_m;
        } else if (self == .seen_d and char == 'o') {
            return .seen_do;
        // do()
        } else if (self == .seen_do and char == '(') {
            return .seen_doo;
        } else if (self == .seen_doo and char == ')') {
            return .finish_do;
        // don't()
        } else if (self == .seen_do and char == 'n') {
            return .seen_don;
        } else if (self == .seen_don and char == '\'') {
            return .seen_dona;
        } else if (self == .seen_dona and char == 't') {
            return .seen_donat;
        } else if (self == .seen_donat and char == '(') {
            return .seen_donato;
        } else if (self == .seen_donato and char == ')') {
            return .finish_donat;
        // mul(nnn,nnn)
        } else if (self == .seen_m and char == 'u') {
            return .seen_mu;
        } else if (self == .seen_mu and char == 'l') {
            return .seen_mul;
        } else if (self == .seen_mul and char == '(') {
            return .seen_mulo;
        } else if (self == .seen_mulo and isDigit(char)) {
            return .seen_mulon;
        } else if (self == .seen_mulon and isDigit(char)) {
            return .seen_mulon;
        } else if (self == .seen_mulon and char == ',') {
            return .seen_mulonc;
        } else if (self == .seen_mulonc and isDigit(char)) {
            return .seen_muloncn;
        } else if (self == .seen_muloncn and isDigit(char)) {
            return .seen_muloncn;
        } else if (self == .seen_muloncn and char == ')') {
            return .finish_mul;
        // all other chars bring us back to start
        } else {
            return .start;
        }
    }
};

fn isDigit(char: u8) bool {
    return switch (char) {
        '0' ... '9' => true,
        else => false,
    };
}

fn readData (allocator: std.mem.Allocator, filename: []const u8) ![]u8 { 

    const file = try std.fs.cwd().openFile(filename, .{});
    defer file.close();
    const stat = try file.stat();
    return try file.readToEndAlloc(allocator, stat.size);
}

Page from Peter's Scrapbook, output from a VimWiki on 2024-12-04.