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; } } };
- Create a separate state for each meaningful step.
- A function is created for the state type.
-
All character "d" steps will reset us to the
seen_d
state. - 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); }