AoC 2024: Day 3

Main thread for Day 3 of the 2024 advent of code. Feel free to discuss the challenge and ask questions. If particular discussions become large, we can break them off into new threads.

Some Rules:

  1. Please try to keep spoilers within spoiler tags. This is done by typing [spoiler]text to be blurred[\spoiler]. If you have longer sections you can also use the [details=“Title”] tags.
  2. Have Fun

Day 3 Challenge

Templates:

Resources:

1910115-f465b6d3

Previous days discussions

Day 1: AoC 2024: Day 1
Day 2: AoC 2024: Day 2

Re-doing it with std.mem.window and std.mem.eql was a nice exercice!

const std = @import("std");
const u = @import("util.zig");

const data = @embedFile("data/day03.txt");

fn mul(win: []const u8) !usize {
    const slice1 = std.mem.sliceTo(win[4..], ',');
    const a = try u.parseInt(usize, slice1, 0);
    const slice2 = std.mem.sliceTo(win[5 + slice1.len ..], ')');
    const b = try u.parseInt(usize, slice2, 0);
    return a * b;
}

fn part1() !void {
    var total: usize = 0;
    var it = std.mem.window(u8, data, 12, 1);
    while (it.next()) |win| {
        if (std.mem.eql(u8, win[0..4], "mul(")) {
            total += mul(win) catch continue;
        }
    }
    u.print("total:{}\n", .{total});
}

fn part2() !void {
    var active: bool = true;
    var total: usize = 0;
    var it = std.mem.window(u8, data, 12, 1);
    while (it.next()) |win| {
        if (std.mem.eql(u8, win[0..4], "do()")) {
            active = true;
        }

        if (std.mem.eql(u8, win[0..7], "don't()")) {
            active = false;
        }

        if (std.mem.eql(u8, win[0..4], "mul(")) {
            if (active) {
                total += mul(win) catch continue;
            }
        }
    }
    u.print("total:{}\n", .{total});
}

pub fn main() !void {
    try part1();
    try part2();
}
2 Likes

Parsing is quite fun since Zig allows naming symbols also

Part 1
const MulParser = struct {
    const Self = @This();

    const State = enum { m, u, l, @"(", @"#1", @"#2", @")" };

    state: State = .m,
    first_num: u64 = 0,
    second_num: u64 = 0,

    pub fn feed(self: *Self, c: u8) bool {
        switch (self.state) {
            .m => {
                if (c == 'm') self.state = .u else self.reset();
            },
            .u => {
                if (c == 'u') self.state = .l else self.reset();
            },
            .l => {
                if (c == 'l') self.state = .@"(" else self.reset();
            },
            .@"(" => {
                if (c == '(') self.state = .@"#1" else self.reset();
            },
            .@"#1" => {
                if ('0' <= c and c <= '9') {
                    self.first_num = (c - '0') + self.first_num * 10;
                } else if (c == ',') {
                    self.state = .@"#2";
                } else {
                    self.reset();
                }
            },
            .@"#2" => {
                if ('0' <= c and c <= '9') {
                    self.second_num = (c - '0') + self.second_num * 10;
                } else if (c == ')') {
                    self.state = .@")";
                    return true;
                } else {
                    self.reset();
                }
            },
            .@")" => {
                self.reset();
                return self.feed(c);
            },
        }

        return false;
    }

    pub fn reset(self: *Self) void {
        self.* = .{};
    }
};

fn partOne(file: std.fs.File) !u64 {
    var result: u64 = 0;
    var parser = MulParser{};
    var buffered_reader = std.io.bufferedReader(file.reader());
    var reader = buffered_reader.reader();

    while (reader.readByte()) |c| {
        if (parser.feed(c)) {
            result += parser.first_num * parser.second_num;
        }
    } else |err| {
        switch (err) {
            error.EndOfStream => {},
            else => return err,
        }
    }

    return result;
}
Part 2
const MulParser = struct {
    const Self = @This();

    const State = enum { m, u, l, @"(", @"#1", @"#2", @")" };

    state: State = .m,
    first_num: u64 = 0,
    second_num: u64 = 0,

    pub fn feed(self: *Self, c: u8) bool {
        switch (self.state) {
            .m => {
                if (c == 'm') self.state = .u else self.reset();
            },
            .u => {
                if (c == 'u') self.state = .l else self.reset();
            },
            .l => {
                if (c == 'l') self.state = .@"(" else self.reset();
            },
            .@"(" => {
                if (c == '(') self.state = .@"#1" else self.reset();
            },
            .@"#1" => {
                if ('0' <= c and c <= '9') {
                    self.first_num = (c - '0') + self.first_num * 10;
                } else if (c == ',') {
                    self.state = .@"#2";
                } else {
                    self.reset();
                }
            },
            .@"#2" => {
                if ('0' <= c and c <= '9') {
                    self.second_num = (c - '0') + self.second_num * 10;
                } else if (c == ')') {
                    self.state = .@")";
                    return true;
                } else {
                    self.reset();
                }
            },
            .@")" => {
                self.reset();
                return self.feed(c);
            },
        }

        return false;
    }

    pub fn reset(self: *Self) void {
        self.* = .{};
    }
};

const DoDontParser = struct {
    const Self = @This();
    const State = enum { d, o, n, @"'", t, @"(", @")" };

    state: State = .d,
    enabled: bool = true,
    is_do: bool = true,

    pub fn feed(self: *Self, c: u8) bool {
        switch (self.state) {
            .d => if (c == 'd') {
                self.state = .o;
                return true;
            },
            .o => if (c == 'o') {
                self.state = .n;
                self.is_do = true;
                return true;
            },
            .n => if (c == 'n') {
                self.state = .@"'";
                self.is_do = false;
                return true;
            } else if (c == '(') {
                self.state = .@")";
                return true;
            },
            .@"'" => if (c == '\'') {
                self.state = .t;
                return true;
            },
            .t => if (c == 't') {
                self.state = .@"(";
                return true;
            },
            .@"(" => if (c == '(') {
                self.state = .@")";
                return true;
            },
            .@")" => if (c == ')') {
                self.enabled = self.is_do;
                self.state = .d;
                return true;
            },
        }

        self.state = .d;
        return false;
    }
};


fn partTwo(file: std.fs.File) !u64 {
    var result: u64 = 0;
    var do_dont_parser = DoDontParser{};
    var mul_parser = MulParser{};
    var buffered_reader = std.io.bufferedReader(file.reader());
    var reader = buffered_reader.reader();

    while (reader.readByte()) |c| {
        if (do_dont_parser.feed(c)) {
            continue;
        }

        if (mul_parser.feed(c) and do_dont_parser.enabled) {
            result += mul_parser.first_num * mul_parser.second_num;
        }
    } else |err| {
        switch (err) {
            error.EndOfStream => {},
            else => return err,
        }
    }

    return result;
}
1 Like

I like the leaderboard lol. Always fun to get fast links to other peoples githubs.

I could clean below a bit, but I won’t :see_no_evil: .

EZ day3
const std = @import("std");
const input = @embedFile("input.txt");

fn part_1(in: []const u8) !i32 {
    var solution: i32 = 0;

    // var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    // defer arena.deinit();
    // const allocator = arena.allocator();

    var it = std.mem.tokenizeScalar(u8, in, '\n');
    // a mul has to be in clean format
    while (it.next()) |token| {
        var mul_slide = std.mem.window(u8, token, 4, 1);
        while (mul_slide.next()) |prefix| {
            if (std.mem.eql(u8, prefix, "mul(")) {
                const instruction = std.mem.sliceTo(token[mul_slide.index.? + 3 ..], ')');
                var numbers = std.mem.splitAny(u8, instruction, ",");

                var number_count: usize = 0;
                var parse_attempt: usize = 0;
                var left_number: ?i32 = null;
                var right_number: ?i32 = null;
                while (numbers.next()) |number| {
                    parse_attempt = parse_attempt + 1;
                    const parsed = std.fmt.parseInt(i32, number, 10) catch break;
                    number_count = number_count + 1;
                    if (left_number == null) left_number = parsed else right_number = parsed;
                }
                if (number_count != 2 or parse_attempt != 2) continue;
                solution = solution + (left_number.? * right_number.?);
            }
        }
    }

    std.debug.print("solution {d}\n", .{solution});
    return solution;
}

fn part_2(in: []const u8) !i32 {
    var solution: i32 = 0;

    // var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    // defer arena.deinit();
    // const allocator = arena.allocator();

    var mul_enabled = true;
    var it = std.mem.tokenizeScalar(u8, in, '\n');
    // a mul has to be in clean format
    while (it.next()) |token| {
        var mul_slide = std.mem.window(u8, token, 7, 1);
        while (mul_slide.next()) |prefix| {
            if (std.mem.eql(u8, prefix, "don't()")) {
                mul_enabled = false;
            }
            if (std.mem.eql(u8, prefix[0..4], "do()")) {
                mul_enabled = true;
            }
            if (std.mem.eql(u8, prefix[0..4], "mul(") and mul_enabled) {
                const instruction = std.mem.sliceTo(token[mul_slide.index.? + 3 ..], ')');
                var numbers = std.mem.splitAny(u8, instruction, ",");

                var number_count: usize = 0;
                var parse_attempt: usize = 0;
                var left_number: ?i32 = null;
                var right_number: ?i32 = null;
                while (numbers.next()) |number| {
                    parse_attempt = parse_attempt + 1;
                    const parsed = std.fmt.parseInt(i32, number, 10) catch break;
                    number_count = number_count + 1;
                    if (left_number == null) left_number = parsed else right_number = parsed;
                }
                if (number_count != 2 or parse_attempt != 2) continue;
                solution = solution + (left_number.? * right_number.?);
            }
        }
    }

    std.debug.print("solution {d}\n", .{solution});
    return solution;
}

pub fn main() !void {
    _ = try part_1(input);
    _ = try part_2(input);
}

test "test" {
    const test_in =
        \\xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))
    ;
    const test_1 = try part_1(test_in);
    try std.testing.expect(test_1 == 161);

    const test_2_in =
        \\xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))
    ;

    const test_2 = try part_2(test_2_in);
    try std.testing.expect(test_2 == 48);
}
1 Like

Shoutout to me getting to use std.mem.window. Ty Mr. Cro

1 Like

For this one, I first “cheated” with a one-liner (sed, tr, egrep, awk) to get the results, and then wrote some nice zig code… Here are the “cheats”:

part 1
$ cat ~/Downloads/input03.txt | sed 's/)/|/g' | tr '|' '\n' | egrep 'mul\([0-9]{1,3},[0-9]{1,3}$' | sed 's/^.*mul(/mul(/g' | tr '[(,]' ' ' | awk '{t += $2 * $3} END {print t}'
part 2
$ cat ~/Downloads/input03.txt | sed 's/)/|/g' | tr '|' '\n' | tr "'" "~" | egrep '(do\(|don~t\(|mul\([0-9]{1,3},[0-9]{1,3})$' | sed -e 's/^.*mul(/mul(/g' -e 's/^.*don~t/dont/g' -e 's/^.*do/do/g' | tr '[(,]' ' ' | awk 'BEGIN {d=1} $1 ~ /^dont$/ {d=0} $1 ~ /^do$/ {d=1} $1 ~ /^mul$/ {if (d) {t+=$2*$3}} END {print t}'
3 Likes
Parts 1 and 2
const std = @import("std");

const input = @embedFile("input.txt");

pub fn main() void {
    std.debug.print("{}\n", .{run(.part1)});
    std.debug.print("{}\n", .{run(.part2)});
}

fn run(part: enum { part1, part2 }) u32 {
    var sum_of_mults: u32 = 0;
    var i: usize = 0;
    while (i < input.len) {
        const start_i = i;
        i += std.mem.indexOf(u8, input[i..], "mul(") orelse break;

        if (part == .part2) {
            // Check for intermediate `don't()`. If one is found, then just skip ahead past the next `do()` and restart the loop.
            if (std.mem.indexOf(u8, input[start_i..i], "don't()")) |relative_dont_i| {
                const after_dont_i = start_i + relative_dont_i + "don't()".len;
                i = after_dont_i + (std.mem.indexOf(u8, input[after_dont_i..], "do()") orelse break);
                i += "do()".len;
                continue;
            }
        }

        i += "mul(".len;

        const lhs_str = lhs_str: {
            var num_len: usize = 0;
            while (i + num_len < input.len and std.ascii.isDigit(input[i + num_len])) num_len += 1;
            if (num_len == 0) continue;
            break :lhs_str input[i..][0..num_len];
        };
        i += lhs_str.len;

        if (i >= input.len or input[i] != ',') continue;
        i += 1;

        const rhs_str = rhs_str: {
            var num_len: usize = 0;
            while (i + num_len < input.len and std.ascii.isDigit(input[i + num_len])) num_len += 1;
            if (num_len == 0) continue;
            break :rhs_str input[i..][0..num_len];
        };
        i += rhs_str.len;

        if (i >= input.len or input[i] != ')') continue;
        i += 1;

        // We know these strings consist only of valid digits
        const lhs = std.fmt.parseInt(u32, lhs_str, 10) catch unreachable;
        const rhs = std.fmt.parseInt(u32, rhs_str, 10) catch unreachable;
        sum_of_mults += lhs * rhs;
    }
    return sum_of_mults;
}
1 Like

std.mem.window is pretty cool

part 1 & 2
const std = @import("std");

const util = @import("util.zig");
const gpa = util.gpa;

const data = @embedFile("data/day03.txt");

pub fn main() !void {
    var p1: i64 = 0;
    var p2: i64 = 0;

    var enabled = true;
    var lines = std.mem.tokenizeScalar(u8, data, '\n');
    outer: while (lines.next()) |line| {
        var it = std.mem.window(u8, line, 7, 1);
        var i: usize = 0;

        while (it.next()) |tok| : (i += 1) {
            if (std.mem.eql(u8, tok[0..4], "do()")) {
                enabled = true;
                continue;
            }

            if (std.mem.eql(u8, tok, "don't()")) {
                enabled = false;
                continue;
            }

            if (std.mem.eql(u8, tok[0..4], "mul(")) {
                const closing_idx = std.mem.indexOfScalarPos(u8, line, i + 7, ')');
                if (closing_idx == null) continue :outer;

                const inner = line[i + 4 .. closing_idx.?];

                var nums_it = std.mem.tokenizeScalar(u8, inner, ',');
                var first: i64 = 0;
                var second: i64 = 0;
                if (nums_it.next()) |val| first = std.fmt.parseInt(i64, val, 10) catch continue else continue;
                if (nums_it.next()) |val| second = std.fmt.parseInt(i64, val, 10) catch continue else continue;
                if (nums_it.peek() != null) continue;

                p1 += first * second;
                if (enabled) {
                    p2 += first * second;
                }
            }
        }
    }

    std.debug.print("part1: {}\n", .{p1});
    std.debug.print("part2: {}\n", .{p2});
}

2 Likes

Thanks @mnemnion for mvzr! Is it cheating to stand on the shoulders of giants ? :wink:

1 Like

A very basic scanning method:

Part 1
const std = @import("std");

const input = @embedFile("input.txt");

pub fn main() !void {
    var sum: u64 = 0;
    var i: usize = 0;
    while (i < input.len - 4) {
        if (!std.mem.eql(u8, input[i .. i + 4], "mul(")) {
            i += 1;
            continue;
        }
        i += 4;

        const first_num = scanNum(input[i..]);
        if (first_num.len == 0) continue;
        i += first_num.len;

        if (input[i] != ',') continue;
        i += 1;

        const second_num = scanNum(input[i..]);
        if (second_num.len == 0) continue;
        i += second_num.len;

        if (input[i] != ')') continue;
        i += 1;

        const a = try std.fmt.parseInt(u32, first_num, 10);
        const b = try std.fmt.parseInt(u32, second_num, 10);
        sum += a * b;
    }

    std.debug.print("sum: {d}\n", .{sum});
}

fn scanNum(buf: []const u8) []const u8 {
    var len: usize = 0;
    for (buf) |c| {
        if (std.ascii.isAlphanumeric(c)) {
            len += 1;
        } else {
            break;
        }
    }
    return buf[0..len];
}
Part 2
const std = @import("std");

const input = @embedFile("input.txt");

pub fn main() !void {
    var sum: u64 = 0;
    var i: usize = 0;
    while (i < input.len) {
        if (match(input[i..], "don't()")) {
            while (!match(input[i..], "do()") and i < input.len) {
                i += 1;
            }
        }

        if (!match(input[i..], "mul(")) {
            i += 1;
            continue;
        }
        i += 4;

        const first_num = scanNum(input[i..]);
        if (first_num.len == 0) continue;
        i += first_num.len;

        if (input[i] != ',') continue;
        i += 1;

        const second_num = scanNum(input[i..]);
        if (second_num.len == 0) continue;
        i += second_num.len;

        if (input[i] != ')') continue;
        i += 1;

        const a = try std.fmt.parseInt(u32, first_num, 10);
        const b = try std.fmt.parseInt(u32, second_num, 10);
        sum += a * b;
    }

    std.debug.print("sum: {d}\n", .{sum});
}

fn match(buf: []const u8, s: []const u8) bool {
    if (s.len > buf.len) return false;
    return std.mem.eql(u8, buf[0..s.len], s);
}

fn scanNum(buf: []const u8) []const u8 {
    var len: usize = 0;
    for (buf) |c| {
        if (std.ascii.isAlphanumeric(c)) {
            len += 1;
        } else {
            break;
        }
    }
    return buf[0..len];
}

Oh My God strap on guys because this is the worst code on earth, the most over engineered solution possible but at least it’s correct :sob:

Part1
const std = @import("std");
const data = @embedFile("inputs/inputs.txt");

const State = enum {
    invalid,
    mul,
    lpar,
    num,
    sep,
    rpar,
    eof,
};

const Iterator = struct {
    const endl: u8 = 255;
    items: []const u8,
    index: usize,

    fn init(items: []const u8) Iterator {
        return .{
            .items = items,
            .index = 0,
        };
    }

    fn at(it: *const Iterator) usize {
        return it.index;
    }

    fn eof(it: *Iterator) bool {
        return if (it.index >= it.items.len) true else false;
    }

    fn curr(it: *Iterator) u8 {
        return it.peek(0);
    }

    fn peek(it: *Iterator, ahead: usize) u8 {
        return if (it.index + ahead >= it.items.len) Iterator.endl else it.items[it.index + ahead];
    }

    fn skip(it: *Iterator, n: usize) usize {
        return for (0..n) |i| {
            if (!it.advance()) break i;
        } else n;
    }

    fn match(it: *Iterator, cond: fn (u8) bool) usize {
        var count: usize = 0;
        while (!it.eof()) : (count += 1) {
            if (!cond(it.peek(1))) {
                it.index -= count;
                return count;
            }
            _ = it.advance();
        }
    }

    fn advance(it: *Iterator) bool {
        if (it.eof()) return (false);
        it.index += 1;
        return (true);
    }
};

pub fn main() !void {
    const input_text = data;
    var result: i64 = 0;
    var buffer: [3]u8 = undefined;
    @memset(&buffer, 0);

    var it = Iterator.init(input_text);
    var state: State = .invalid;
    var lhs: i32 = 0;
    var rhs: i32 = 0;
    var num_idx: usize = 0;

    while (!it.eof()) {
        state = switch (state) {
            State.invalid => st: {
                if (it.curr() == 'm' and it.peek(1) == 'u' and it.peek(2) == 'l') {
                    _ = it.skip(3);
                    break :st State.mul;
                } else {
                    _ = it.advance();
                    break :st State.invalid;
                }
            },

            State.mul => st: {
                if (it.curr() == '(') {
                    _ = it.advance();
                    break :st State.lpar;
                } else {
                    break :st State.invalid;
                }
            },

            State.lpar => st: {
                if (it.curr() >= '0' and it.curr() <= '9') {
                    num_idx = 0;
                    while (it.curr() >= '0' and it.curr() <= '9' and num_idx < buffer.len) : (num_idx += 1) {
                        buffer[num_idx] = it.curr();
                        _ = it.advance();
                    }
                    lhs = try std.fmt.parseInt(i32, buffer[0..num_idx], 10);
                    @memset(&buffer, 0);
                    num_idx = 0;
                    if (it.curr() == ',') {
                        _ = it.advance();
                        break :st State.sep;
                    } else {
                        break :st State.invalid;
                    }
                } else {
                    _ = it.advance();
                    break :st State.invalid;
                }
            },

            State.sep => st: {
                if (it.curr() >= '0' and it.curr() <= '9') {
                    num_idx = 0;
                    while (it.curr() >= '0' and it.curr() <= '9' and num_idx < buffer.len) : (num_idx += 1) {
                        buffer[num_idx] = it.curr();
                        _ = it.advance();
                    }
                    rhs = try std.fmt.parseInt(i32, buffer[0..num_idx], 10);
                    @memset(&buffer, 0);
                    num_idx = 0;
                    if (it.curr() == ')') {
                        _ = it.advance();
                        break :st State.rpar;
                    } else {
                        break :st State.invalid;
                    }
                } else {
                    _ = it.advance();
                    break :st State.invalid;
                }
            },

            State.rpar => st: {
                result += lhs * rhs;
                break :st State.invalid;
            },

            else => st: {
                _ = it.advance();
                break :st State.invalid;
            },
        };
    }

    std.debug.print("result1 = {d}\n", .{result});
}
Part2
const std = @import("std");
const data = @embedFile("inputs/inputs.txt");

const State = enum {
    invalid,
    mul,
    lpar,
    num,
    sep,
    rpar,
    eof,
};

const Iterator = struct {
    const endl: u8 = 255;
    items: []const u8,
    index: usize,

    fn init(items: []const u8) Iterator {
        return .{
            .items = items,
            .index = 0,
        };
    }

    fn at(it: *const Iterator) usize {
        return it.index;
    }

    fn eof(it: *Iterator) bool {
        return if (it.index >= it.items.len) true else false;
    }

    fn curr(it: *Iterator) u8 {
        return it.peek(0);
    }

    fn peek(it: *Iterator, ahead: usize) u8 {
        return if (it.index + ahead >= it.items.len) Iterator.endl else it.items[it.index + ahead];
    }

    fn skip(it: *Iterator, n: usize) usize {
        return for (0..n) |i| {
            if (!it.advance()) break i;
        } else n;
    }

    fn match(it: *Iterator, cond: fn (u8) bool) usize {
        var count: usize = 0;
        while (!it.eof()) : (count += 1) {
            if (!cond(it.peek(1))) {
                it.index -= count;
                return count;
            }
            _ = it.advance();
        }
    }

    fn advance(it: *Iterator) bool {
        if (it.eof()) return (false);
        it.index += 1;
        return (true);
    }
};

pub fn main() !void {
    const input_text = data;
    var result: i64 = 0;
    var buffer: [3]u8 = undefined;
    @memset(&buffer, 0);

    var it = Iterator.init(input_text);
    var state: State = .invalid;
    var do_enabled: bool = true;
    var lhs: i32 = 0;
    var rhs: i32 = 0;
    var num_idx: usize = 0;

    while (!it.eof()) {
        state = switch (state) {
            State.invalid => st: {
                if (it.curr() == 'm' and it.peek(1) == 'u' and it.peek(2) == 'l') {
                    _ = it.skip(3);
                    break :st State.mul;
                } else if (it.curr() == 'd' and it.peek(1) == 'o' and it.peek(2) == '(' and it.peek(3) == ')') {
                    do_enabled = true;
                    _ = it.skip(4);
                    break :st State.invalid;
                } else if (it.curr() == 'd' and it.peek(1) == 'o' and it.peek(2) == 'n' and it.peek(3) == '\'' and it.peek(4) == 't' and it.peek(5) == '(' and it.peek(6) == ')') {
                    do_enabled = false;
                    _ = it.skip(7);
                    break :st State.invalid;
                } else {
                    _ = it.advance();
                    break :st State.invalid;
                }
            },

            State.mul => st: {
                if (it.curr() == '(') {
                    _ = it.advance();
                    break :st State.lpar;
                } else {
                    break :st State.invalid;
                }
            },

            State.lpar => st: {
                if (it.curr() >= '0' and it.curr() <= '9') {
                    num_idx = 0;
                    while (it.curr() >= '0' and it.curr() <= '9' and num_idx < buffer.len) : (num_idx += 1) {
                        buffer[num_idx] = it.curr();
                        _ = it.advance();
                    }
                    lhs = try std.fmt.parseInt(i32, buffer[0..num_idx], 10);
                    @memset(&buffer, 0);
                    num_idx = 0;
                    if (it.curr() == ',') {
                        _ = it.advance();
                        break :st State.sep;
                    } else {
                        break :st State.invalid;
                    }
                } else {
                    _ = it.advance();
                    break :st State.invalid;
                }
            },

            State.sep => st: {
                if (it.curr() >= '0' and it.curr() <= '9') {
                    num_idx = 0;
                    while (it.curr() >= '0' and it.curr() <= '9' and num_idx < buffer.len) : (num_idx += 1) {
                        buffer[num_idx] = it.curr();
                        _ = it.advance();
                    }
                    rhs = try std.fmt.parseInt(i32, buffer[0..num_idx], 10);
                    @memset(&buffer, 0);
                    num_idx = 0;
                    if (it.curr() == ')') {
                        _ = it.advance();
                        break :st State.rpar;
                    } else {
                        break :st State.invalid;
                    }
                } else {
                    _ = it.advance();
                    break :st State.invalid;
                }
            },

            State.rpar => st: {
                if (do_enabled) {
                    result += (lhs * rhs);
                }
                break :st State.invalid;
            },

            else => st: {
                _ = it.advance();
                break :st State.invalid;
            },
        };
    }

    std.debug.print("result = {d}\n", .{result});
}

1 Like

I ended up falling asleep working on part 1 yesterday. I’ll try to wrap up part two and then share my solution. I think this was the first challenge that allowed for more “freedom” in solving it.

Feeling pretty good about this one! I really like my solution and got it done quick.

main.zig
const std = @import("std");

pub fn main() !void {
    const input = try std.fs.cwd().openFile("src/3/input", .{});
    defer input.close();
    var bufreader = std.io.bufferedReader(input.reader());
    const reader = bufreader.reader();

    const stdout = std.io.getStdOut();
    defer stdout.close();
    try stdout.writer().print("{d}\n", .{try calculate(reader, false)});
    try input.seekTo(0);
    try stdout.writer().print("{d}\n", .{try calculate(reader, true)});
}

fn calculate(reader: anytype, disables: bool) !u32 {
    const end_byte = ')';
    const start_str = "mul(";

    var do = true;
    var sum: u32 = 0;
    var buf: [1024]u8 = undefined;
    while (try reader.readUntilDelimiterOrEof(&buf, end_byte)) |str| {
        do = (do or std.mem.endsWith(u8, str, "do(")) and !std.mem.endsWith(u8, str, "don't(");
        if (!do and disables)
            continue;

        const start = std.mem.lastIndexOf(u8, str, start_str) orelse continue;
        const nums = str[start + start_str.len ..];
        const split = std.mem.indexOfScalar(u8, nums, ',') orelse continue;
        const ln = std.fmt.parseInt(u32, nums[0..split], 10) catch continue;
        const rn = std.fmt.parseInt(u32, nums[split + 1 ..], 10) catch continue;
        sum += ln * rn;
    }
    return sum;
}

test {
    const bytes = "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))";
    var stream = std.io.fixedBufferStream(bytes);
    try std.testing.expectEqual(161, try calculate(stream.reader()));
}
2 Likes
my solution
const std = @import("std");
const mem = std.mem;
const fmt = std.fmt;
const Instant = std.time.Instant;
const print = std.debug.print;

fn dummyLog(comptime s: []const u8, args: anytype) void {
    _ = s;
    _ = args;
}
const verbose = false;
const log = if (verbose) print else dummyLog;

const data = @embedFile("data/day03.txt");

pub fn main() !void {
    const start_time = try Instant.now();
    log("{s}\n", .{data});

    var sum1: usize = 0;
    var sum2: usize = 0;

    var pos: usize = 0;
    while (pos < data.len) {
        const index_of_mul: usize = mem.indexOfPos(u8, data, pos, "mul(") orelse break;
        log("found mul( at index {}\n", .{index_of_mul});

        pos = index_of_mul + 4;

        const index_of_comma: usize = mem.indexOfScalarPos(u8, data, pos, ',') orelse continue;
        log("found , at index {}\n", .{index_of_comma});

        if (index_of_comma - pos > 4) {
            continue;
        }
        const number_substring_1 = data[pos..index_of_comma];
        log("found first number string {s}, between index {} and {}\n", .{ number_substring_1, pos, index_of_comma });

        const number_1: usize = fmt.parseInt(usize, number_substring_1, 10) catch continue;
        log("found first number {}\n", .{number_1});

        pos = index_of_comma + 1;

        const index_of_final_paren: usize = mem.indexOfScalarPos(u8, data, pos, ')') orelse continue;
        log("found final ) at index {}\n", .{index_of_final_paren});

        if (index_of_final_paren - pos > 4) {
            continue;
        }
        const number_substring_2 = data[pos..index_of_final_paren];
        log("found second number string {s}, between index {} and {}\n", .{ number_substring_2, pos, index_of_final_paren });

        const number_2: usize = fmt.parseInt(usize, number_substring_2, 10) catch continue;
        log("found second number {}\n", .{number_2});

        log("found valid mul({},{})\n", .{ number_1, number_2 });

        const all_data_before_mul = data[0..index_of_mul];
        log("searching for do() and don't() in string: {s}\n", .{all_data_before_mul});
        const index_of_previous_do = mem.lastIndexOf(u8, all_data_before_mul, "do()") orelse 0;
        log("index of previous do(): {}\n", .{index_of_previous_do});
        const index_of_previous_dont = mem.lastIndexOf(u8, all_data_before_mul, "don't()") orelse 0;
        log("index of previous don't(): {}\n", .{index_of_previous_dont});

        var enabled = true;
        if (index_of_previous_dont > index_of_previous_do) {
            enabled = false;
            log("disabling because don't() is closer than do()", .{});
        }
        const product = number_1 * number_2;
        sum1 += product;
        if (enabled) {
            sum2 += product;
        }
    }

    print("time: {}us\n", .{(try Instant.now()).since(start_time) / 1000});

    print("answer1: {}\n", .{sum1});
    print("answer2: {}\n", .{sum2});

    //> zig build day03 -freference-trace -Doptimize=ReleaseFast
    // time: 491us
    // answer1: 165225049
    // answer2: 108830766
}

A little overengineered, but it was fun to write. Inspired by the Zig compiler tokenizer use of labeled switch statements.

Parts 1 & 2
const std = @import("std");

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

    const src = try readFileAlloc(allocator, "data.txt");

    var lexer = Lexer.init(src, true);
    lexer.multiply_all() catch |err| {
        std.debug.print("Error: {any}\n", .{err});
    };
    std.debug.print("1 - Sum of all products: {d}\n", .{lexer.total});

    lexer = Lexer.init(src, false);
    lexer.multiply_all() catch |err| {
        std.debug.print("Error: {any}\n", .{err});
    };
    std.debug.print("2 - Sum of all products: {d}\n", .{lexer.total});
}

pub const Lexer = struct {
    buffer: [:0]const u8,
    index: usize,
    total: u32,
    enable_mul: bool,
    first_part: bool,

    pub fn init(buffer: [:0]const u8, first_part: bool) Lexer {
        return .{
            .buffer = buffer,
            .index = 0,
            .total = 0,
            .enable_mul = true,
            .first_part = first_part,
        };
    }

    const State = enum {
        start,
        first_num,
        second_num,
    };

    pub fn multiply_all(self: *Lexer) !void {
        var first_number: u32 = undefined;
        var idx_first_number: usize = undefined;
        var second_number: u32 = undefined;
        var idx_second_number: usize = undefined;

        state: switch (State.start) {
            .start => switch (self.buffer[self.index]) {
                0 => {
                    if (self.index == self.buffer.len) {
                        return;
                    }
                },
                'm' => {
                    if (self.index + 4 > self.buffer.len) {
                        return;
                    }
                    if (std.mem.eql(u8, self.buffer[self.index .. self.index + 4], "mul(")) {
                        self.index += 4;
                        idx_first_number = self.index;
                        continue :state .first_num;
                    } else {
                        self.index += 1;
                        continue :state .start;
                    }
                },
                'd' => {
                    if (self.first_part) {
                        self.index += 1;
                        continue :state .start;
                    }
                    if (self.index + 4 > self.buffer.len) {
                        return;
                    }
                    if (std.mem.eql(u8, self.buffer[self.index .. self.index + 4], "do()")) {
                        self.index += 4;
                        self.enable_mul = true;
                        continue :state .start;
                    }
                    if (self.index + 7 > self.buffer.len) {
                        return;
                    }
                    if (std.mem.eql(u8, self.buffer[self.index .. self.index + 7], "don't()")) {
                        self.index += 7;
                        self.enable_mul = false;
                        continue :state .start;
                    }

                    self.index += 1;
                    continue :state .start;
                },
                else => {
                    self.index += 1;
                    continue :state .start;
                },
            },
            .first_num => switch (self.buffer[self.index]) {
                '0'...'9' => {
                    self.index += 1;
                    continue :state .first_num;
                },
                ',' => {
                    first_number = try std.fmt.parseInt(u32, self.buffer[idx_first_number..self.index], 10);
                    self.index += 1;
                    idx_second_number = self.index;
                    continue :state .second_num;
                },
                else => {
                    continue :state .start;
                },
            },
            .second_num => switch (self.buffer[self.index]) {
                '0'...'9' => {
                    self.index += 1;
                    continue :state .second_num;
                },
                ')' => {
                    second_number = try std.fmt.parseInt(u32, self.buffer[idx_second_number..self.index], 10);
                    self.index += 1;
                    if (self.enable_mul) {
                        self.total += (first_number * second_number);
                    }
                    continue :state .start;
                },
                else => {
                    continue :state .start;
                },
            },
        }
    }
};

pub fn readFileAlloc(allocator: std.mem.Allocator, file_path: []const u8) ![:0]const u8 {
    const content = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024);
    const null_terminated = try allocator.alloc(u8, content.len + 1);
    @memcpy(null_terminated[0..content.len], content);
    null_terminated[content.len] = 0;
    return null_terminated[0..content.len :0];
}
2 Likes

Finally got around to finishing part 2. I decided to build out a lexer parser for this, rather than reach for regex. It was fun.

Code
const std = @import("std");
const fs = std.fs;
const Allocator= std.mem.Allocator;
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const global_alloc = arena.allocator();
const max_read = 20 * 1024;


pub fn main() !void {
    defer arena.deinit();
    const data = try loadData(global_alloc, "data/prod.txt");
    const tokens = try lex(global_alloc, data);
    parseTokens(tokens);
}

/// Load the Data from path
fn loadData(allocator: Allocator, path: []const u8) ![]u8 {
    const fd = try fs.cwd().openFile(path, .{});
    return try fd.readToEndAlloc(allocator, max_read);
}

fn parseTokens(tokens: []Token) void {
    var pp: usize = 0;
    var sum: isize = 0;
    var on = true;
    while (pp < tokens.len) {
        if (tokens[pp] == .on) {
            on = true;
            pp += 1;
        }
        if (tokens[pp] == .off) {
            on = false;
            pp += 1;
        }
        if (!on) {
            pp += 1;
            continue;
        }
        if (parseEquation(tokens[pp..])) |eq| {
            pp += 5;
            sum += eq.a * eq.b;
        } else {
            pp += 1;
        }
    }
    std.debug.print("The sum is: {d}", .{sum});
}

fn parseEquation(tokens: []Token) ?Equation {
    if (tokens.len < 5) {
        return null;
    }
    if (tokens[0] == .mul_start and 
        tokens[1] == .digit and
        tokens[2] == .comma and
        tokens[3] == .digit and
        tokens[4] == .mul_end) {
        return Equation{.a = tokens[1].digit, .b = tokens[3].digit};
    } else {
        return null;
    }
}

const Equation = struct {
    a: isize,
    b: isize,
};

/// Lets build a simple parser, rather than reach for regex
const Token = union(enum) {
    mul_start,
    digit: isize,
    mul_end,
    comma,
    garbage,
    on,
    off,
};

/// Read in data and generate a stream of tokens
fn lex(allocator: Allocator, bytes: []const u8) ![]Token {
    var list = std.ArrayList(Token).init(allocator);
    var pp: usize = 0;
    while (pp < bytes.len) {
        const byte = bytes[pp];
        switch (byte) {
            'd' => {
                if(std.mem.startsWith(u8, bytes[pp..], "do()")) {
                    try list.append(.on);
                    pp += 4;
                } else if (std.mem.startsWith(u8, bytes[pp..], "don't()")) {
                    try list.append(.off);
                    pp += 7;
                } else {
                    pp += 1;
                }
            },
            'm' => {
                if (std.mem.eql(u8, "mul(", bytes[pp..pp+4])) {
                    try list.append(.mul_start);
                    pp += 4;
                } else {
                    try list.append(.garbage);
                    pp += 1;
                }
            },
            ')' => {try list.append(.mul_end); pp+=1;},
            ',' => {try list.append(.comma); pp+=1;},
           0x30...0x39 => {
               var x: usize = 1;
               while (x < 3): (x+=1) {
                   if (bytes[pp+x] < 0x30 or bytes[pp+x] > 0x39) {
                       try list.append(.{.digit=try std.fmt.parseInt(isize, bytes[pp..pp+x], 10)});
                       break;
                   }
               } else {
                   try list.append(.{.digit=try std.fmt.parseInt(isize, bytes[pp..pp+x], 10)});
               }
               pp += x;
           },
           else => {
               try list.append(.garbage);
               pp += 1;
           }
        }
    }
    return list.toOwnedSlice();
}

I wrote out a Finite State Machine for this one:

My solution
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,
    };
}

// Read in the data from given filename
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);
}