AoC 2025: Day 3

Main thread for Day 3 of the 2025 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.

Notice that Advent of Code has changed it’s schedule and there will only be 12 days of puzzles this year.

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. If you use the new WYSIWYG editor, remember to close the details section, as it is opened by default. This is an issue (feature?) of the newest discourse updates. If you use markdown, then you can remove the “open" parameter (not in by default unless you use the WYSIWYG editor)
  2. Have Fun

Day 3 Challenge

Templates:

Resources:

2 Likes

Pretty much the same idea as truly-not-taken.

const std = @import("std");

pub fn main() void {
    const input = @embedFile("03.txt");

    var part: [2]usize = @splat(0);

    var it = std.mem.tokenizeAny(u8, input, ",\n");
    while (it.next()) |bank| {
        inline for (.{ 2, 12 }, 0..) |count, j| {
            var b: [count]usize = @splat(0);
            var l: usize = 0;

            for (0..count) |c| {
                for (l..bank.len + c + 1 - count) |i| {
                    const v = bank[i] - '0';
                    if (v > b[c]) {
                        b[c] = v;
                        l = i + 1;
                    }
                }
            }

            var n: usize = 0;
            for (0..count) |c| n = n * 10 + b[c];
            part[j] += n;
        }
    }

    std.debug.print(
        \\1: {}
        \\2: {}
        \\
    , .{ part[0], part[1] });
}
2 Likes

cleaned up a lot from last night https://zigbin.io/4278bb

The puzzle is pretty straightforward.

var sum: [2]usize = @splat(0);
var buffer: [12]u8 = undefined;
const batteries = [2]comptime_int{ 2, 12 };
while (try reader.takeDelimiter('\n')) |line| {
    inline for (0..2) |part| {
        var next: usize = 0;
        for (buffer[0..batteries[part]], 0..) |*digit, i| {
            const index = std.mem.indexOfMax(u8, line[next .. line.len + i + 1 - batteries[part]]);
            digit.* = line[next + index];
            next += index + 1;
        }
        sum[part] += std.fmt.parseInt(usize, buffer[0..batteries[part]], 10) catch |err| switch (err) {
            error.Overflow => unreachable,
            else => |e| return e,
        };
    }
}
try writer.print("{}\n{}\n", .{ sum[0], sum[1] });
try writer.flush();
Part1
const std = @import("std");

const Batteries = struct {
    list: std.array_list.Managed(u8),
    nbanks: usize,
    nperbank: usize,

    const Self = @This();

    pub fn init(input: []const u8, allocator: std.mem.Allocator) !Self {
        var batteries = Self{
            .list = std.array_list.Managed(u8).init(allocator),
            .nbanks = 0,
            .nperbank = 0,
        };

        var lines = std.mem.tokenizeScalar(u8, input, '\n');
        while (lines.next()) |line| : (batteries.nbanks += 1) {
            for (line) |battery| {
                try batteries.list.append(battery - '0');
            }
        }
        batteries.nperbank = @divExact(batteries.list.items.len, batteries.nbanks);
        return batteries;
    }

    pub fn deinit(self: *Self) void {
        self.list.deinit();
    }

    pub fn joltage(self: Self, ibank: usize) u8 {
        var maxjoltage: u8 = 0;
        for (0..self.nperbank - 1) |fst| {
            const fstjoltage = 10 * self.list.items[ibank * self.nperbank + fst];
            if (fstjoltage + 9 < maxjoltage) {
                continue;
            }
            for (fst + 1..self.nperbank) |snd| {
                const sndjoltage = self.list.items[ibank * self.nperbank + snd];
                if (fstjoltage + sndjoltage > maxjoltage) {
                    maxjoltage = fstjoltage + sndjoltage;
                    if (sndjoltage == 9) {
                        break;
                    }
                }
            }
        }
        return maxjoltage;
    }
};

pub fn main() !void {
    var dba = std.heap.DebugAllocator(.{}){};
    defer _ = dba.deinit();
    const allocator = dba.allocator();

    var stdout_buffer: [1024]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
    const stdout = &stdout_writer.interface;

    var stderr_buffer: [1024]u8 = undefined;
    var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer);
    const stderr = &stderr_writer.interface;

    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);
    if (args.len < 2) {
        try stderr.print("Usage: {s} <filename>\n", .{args[0]});
        try stderr.flush();
        return error.InvalidArguments;
    }
    const filename = args[1];
    const file = try std.fs.cwd().openFile(filename, .{});
    defer file.close();
    const file_size = try file.getEndPos();

    const input = try allocator.alloc(u8, file_size);
    defer allocator.free(input);

    const bytes_read = try file.readAll(input);
    if (bytes_read != file_size) {
        try stderr.print("Warning: Read {d} bytes but expected {d}\n", .{ bytes_read, file_size });
    }

    var batteries = try Batteries.init(input, allocator);
    defer batteries.deinit();

    var totaljoltage: usize = 0;
    for (0..batteries.nbanks) |ibank| {
        totaljoltage += batteries.joltage(ibank);
    }

    try stdout.print("Total output jolatages: {d}\n", .{totaljoltage});
    try stdout.flush();
}

Had to do a lot of checks for the first try of part two, in order to break or skip loops early, so that it runs in reasonable time.

Part2 - first try (runtime 2.5s)
const std = @import("std");

const Batteries = struct {
    list: std.array_list.Managed(u8),
    nbanks: usize,
    nperbank: usize,

    const Self = @This();

    pub fn init(input: []const u8, allocator: std.mem.Allocator) !Self {
        var batteries = Self{
            .list = std.array_list.Managed(u8).init(allocator),
            .nbanks = 0,
            .nperbank = 0,
        };

        var lines = std.mem.tokenizeScalar(u8, input, '\n');
        while (lines.next()) |line| : (batteries.nbanks += 1) {
            for (line) |battery| {
                try batteries.list.append(battery - '0');
            }
        }
        batteries.nperbank = @divExact(batteries.list.items.len, batteries.nbanks);
        return batteries;
    }

    pub fn deinit(self: *Self) void {
        self.list.deinit();
    }

    pub fn joltage(self: Self, ibank: usize) usize {
        var maxjoltage: usize = 0;
        for (0..self.nperbank - 11) |idx1| {
            const joltage1: usize = @as(usize, 100000000000) * self.list.items[ibank * self.nperbank + idx1];
            const tmpjoltage1 = joltage1;
            if (tmpjoltage1 + 99999999999 < maxjoltage) {
                continue;
            }
            for (idx1 + 1..self.nperbank - 10) |idx2| {
                const joltage2: usize = @as(usize, 10000000000) * self.list.items[ibank * self.nperbank + idx2];
                const tmpjoltage2 = tmpjoltage1 + joltage2;
                if (tmpjoltage2 + 9999999999 < maxjoltage) {
                    continue;
                }
                for (idx2 + 1..self.nperbank - 9) |idx3| {
                    const joltage3: usize = @as(usize, 1000000000) * self.list.items[ibank * self.nperbank + idx3];
                    const tmpjoltage3 = tmpjoltage2 + joltage3;
                    if (tmpjoltage3 + 999999999 < maxjoltage) {
                        continue;
                    }
                    for (idx3 + 1..self.nperbank - 8) |idx4| {
                        const joltage4: usize = @as(usize, 100000000) * self.list.items[ibank * self.nperbank + idx4];
                        const tmpjoltage4 = tmpjoltage3 + joltage4;
                        if (tmpjoltage4 + 99999999 < maxjoltage) {
                            continue;
                        }
                        for (idx4 + 1..self.nperbank - 7) |idx5| {
                            const joltage5: usize = @as(usize, 10000000) * self.list.items[ibank * self.nperbank + idx5];
                            const tmpjoltage5 = tmpjoltage4 + joltage5;
                            if (tmpjoltage5 + 9999999 < maxjoltage) {
                                continue;
                            }
                            for (idx5 + 1..self.nperbank - 6) |idx6| {
                                const joltage6: usize = @as(usize, 1000000) * self.list.items[ibank * self.nperbank + idx6];
                                const tmpjoltage6 = tmpjoltage5 + joltage6;
                                if (tmpjoltage6 + 999999 < maxjoltage) {
                                    continue;
                                }
                                for (idx6 + 1..self.nperbank - 5) |idx7| {
                                    const joltage7: usize = @as(usize, 100000) * self.list.items[ibank * self.nperbank + idx7];
                                    const tmpjoltage7 = tmpjoltage6 + joltage7;
                                    if (tmpjoltage7 + 99999 < maxjoltage) {
                                        continue;
                                    }
                                    for (idx7 + 1..self.nperbank - 4) |idx8| {
                                        const joltage8: usize = @as(usize, 10000) * self.list.items[ibank * self.nperbank + idx8];
                                        const tmpjoltage8 = tmpjoltage7 + joltage8;
                                        if (tmpjoltage8 + 9999 < maxjoltage) {
                                            continue;
                                        }
                                        for (idx8 + 1..self.nperbank - 3) |idx9| {
                                            const joltage9: usize = @as(usize, 1000) * self.list.items[ibank * self.nperbank + idx9];
                                            const tmpjoltage9 = tmpjoltage8 + joltage9;
                                            if (tmpjoltage9 + 999 < maxjoltage) {
                                                continue;
                                            }
                                            for (idx9 + 1..self.nperbank - 2) |idx10| {
                                                const joltage10: usize = @as(usize, 100) * self.list.items[ibank * self.nperbank + idx10];
                                                const tmpjoltage10 = tmpjoltage9 + joltage10;
                                                if (tmpjoltage10 + 99 < maxjoltage) {
                                                    continue;
                                                }
                                                for (idx10 + 1..self.nperbank - 1) |idx11| {
                                                    const joltage11: usize = @as(usize, 10) * self.list.items[ibank * self.nperbank + idx11];
                                                    const tmpjoltage11 = tmpjoltage10 + joltage11;
                                                    if (tmpjoltage11 + 9 < maxjoltage) {
                                                        continue;
                                                    }
                                                    for (idx11 + 1..self.nperbank - 0) |idx12| {
                                                        const joltage12: usize = 1 * self.list.items[ibank * self.nperbank + idx12];
                                                        const tmpjoltage12 = tmpjoltage11 + joltage12;
                                                        if (tmpjoltage12 > maxjoltage) {
                                                            maxjoltage = tmpjoltage12;
                                                            if (joltage12 == 9) {
                                                                break;
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return maxjoltage;
    }
};

pub fn main() !void {
    var dba = std.heap.DebugAllocator(.{}){};
    defer _ = dba.deinit();
    const allocator = dba.allocator();

    var stdout_buffer: [1024]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
    const stdout = &stdout_writer.interface;

    var stderr_buffer: [1024]u8 = undefined;
    var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer);
    const stderr = &stderr_writer.interface;

    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);
    if (args.len < 2) {
        try stderr.print("Usage: {s} <filename>\n", .{args[0]});
        try stderr.flush();
        return error.InvalidArguments;
    }
    const filename = args[1];
    const file = try std.fs.cwd().openFile(filename, .{});
    defer file.close();
    const file_size = try file.getEndPos();

    const input = try allocator.alloc(u8, file_size);
    defer allocator.free(input);

    const bytes_read = try file.readAll(input);
    if (bytes_read != file_size) {
        try stderr.print("Warning: Read {d} bytes but expected {d}\n", .{ bytes_read, file_size });
    }

    var batteries = try Batteries.init(input, allocator);
    defer batteries.deinit();

    var totaljoltage: usize = 0;
    for (0..batteries.nbanks) |ibank| {
        const joltage = batteries.joltage(ibank);
        totaljoltage += joltage;
    }

    try stdout.print("Total output jolatages: {d}\n", .{totaljoltage});
    try stdout.flush();
}
Part2 - second try (runtime 250 µs)
const std = @import("std");

const Batteries = struct {
    list: std.array_list.Managed(u8),
    nbanks: usize,
    nperbank: usize,

    const Self = @This();

    pub fn init(input: []const u8, allocator: std.mem.Allocator) !Self {
        var batteries = Self{
            .list = std.array_list.Managed(u8).init(allocator),
            .nbanks = 0,
            .nperbank = 0,
        };

        var lines = std.mem.tokenizeScalar(u8, input, '\n');
        while (lines.next()) |line| : (batteries.nbanks += 1) {
            for (line) |battery| {
                try batteries.list.append(battery - '0');
            }
        }
        batteries.nperbank = @divExact(batteries.list.items.len, batteries.nbanks);
        return batteries;
    }

    pub fn deinit(self: *Self) void {
        self.list.deinit();
    }

    pub fn joltage(self: Self, ibank: usize) usize {
        const n_on: usize = 12;
        var bestidx: usize = 0;
        var bestjoltage: u8 = 0;
        var final_joltage: usize = 0;
        bestjoltage = 0;
        for (0..self.nperbank - n_on) |idx| {
            const bat_val = self.list.items[ibank * self.nperbank + idx];
            if (bat_val > bestjoltage) {
                bestjoltage = bat_val;
                bestidx = idx;
            }
        }
        final_joltage = bestjoltage;

        for (1..n_on) |idig| {
            bestjoltage = 0;
            for (bestidx + 1..self.nperbank - (n_on - idig) + 1) |idx| {
                const bat_val = self.list.items[ibank * self.nperbank + idx];
                if (bat_val > bestjoltage) {
                    bestjoltage = bat_val;
                    bestidx = idx;
                }
            }
            final_joltage *= 10;
            final_joltage += bestjoltage;
        }
        return final_joltage;
    }
};

pub fn main() !void {
    var dba = std.heap.DebugAllocator(.{}){};
    defer _ = dba.deinit();
    const allocator = dba.allocator();

    var stdout_buffer: [1024]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
    const stdout = &stdout_writer.interface;

    var stderr_buffer: [1024]u8 = undefined;
    var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer);
    const stderr = &stderr_writer.interface;

    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);
    if (args.len < 2) {
        try stderr.print("Usage: {s} <filename>\n", .{args[0]});
        try stderr.flush();
        return error.InvalidArguments;
    }
    const filename = args[1];
    const file = try std.fs.cwd().openFile(filename, .{});
    defer file.close();
    const file_size = try file.getEndPos();

    const input = try allocator.alloc(u8, file_size);
    defer allocator.free(input);

    const bytes_read = try file.readAll(input);
    if (bytes_read != file_size) {
        try stderr.print("Warning: Read {d} bytes but expected {d}\n", .{ bytes_read, file_size });
    }

    var batteries = try Batteries.init(input, allocator);
    defer batteries.deinit();

    //batteries.debug_print();
    var totaljoltage: usize = 0;
    for (0..batteries.nbanks) |ibank| {
        const joltage = batteries.joltage(ibank);
        //std.debug.print("{d}/{d}: {d}\n", .{ ibank + 1, batteries.nbanks, joltage });
        totaljoltage += joltage;
    }

    try stdout.print("Total output jolatages: {d}\n", .{totaljoltage});
    try stdout.flush();
}
const std = @import("std");

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

pub fn main() void {
    var totalOutput1: usize = 0;
    var totalOutput2: usize = 0;
    var iter = std.mem.splitScalar(u8, data, '\n');
    while (iter.next()) |bank| {
        if (bank.len == 0) continue;
        totalOutput1 += getMaxValue(bank, 2);
        totalOutput2 += getMaxValue(bank, 12);
    }
    std.debug.print("Part 1: {d}\n", .{totalOutput1});
    std.debug.print("Part 2: {d}\n", .{totalOutput2});
}

fn getMaxValue(bank: []const u8, num_digits: u8) usize {
    var value: usize = 0;
    var digit = num_digits;
    var idx: usize = 0;
    var base = std.math.pow(usize, 10, digit - 1);
    while (digit != 0) {
        digit -= 1;
        var max_digit: u8 = bank[idx] - '0';
        for (idx + 1..bank.len - digit) |i| {
            const c = bank[i] - '0';
            if (c > max_digit) {
                max_digit = c;
                idx = i;
            }
        }
        value += base * max_digit;
        idx += 1;
        base /= 10;
    }
    return value;
}

Was a pretty simple day today.

Day 3, part 1:
  - Took 17.809us on average over 1000 runs
Day 3, part 2:
  - Took 38.675us on average over 1000 runs

fn solution(r: *Reader, _: Io, _: Allocator) !u64 {
    var total_joltage: u64 = 0;

    const digits = 12;
    while (try r.takeDelimiter('\n')) |line| {
        assert(line.len >= digits);
        total_joltage += findNDigits(digits, line);
    }

    return total_joltage;
}

fn findNDigits(n: usize, line: []const u8) u64 {
    const Entry = struct { value: u8, idx: usize };
    assert(n > 0);
    assert(line.len >= n);

    var res: u64 = 0;

    var search = line[0 .. line.len - n];
    for (0..n) |_| {
        search.len += 1;

        var max: Entry = .{ .value = 0, .idx = 0 };
        for (search[0..], 0..) |c, idx| {
            assert(switch (c) {
                '0'...'9' => true,
                else => false,
            });
            if (c > max.value) max = .{ .value = c, .idx = idx };
        }

        res *= 10;
        res += max.value - '0';
        search = search[max.idx + 1 ..];
    }

    return res;
}
1 Like

Very straightforward implementation this time around.

Part 2 took 51.4µs average across 1000 runs.

pub fn part2(input: []const u8) usize {
    var total_joltage: usize = 0;
    var line_iter = mem.splitScalar(u8, input, '\n');
    while (line_iter.next()) |line| {
        if (line.len == 0) continue;
        var to_add: usize = 0;
        var i: usize = 0;
        for (0..12) |digit| {
            i += mem.findMax(u8, line[i .. line.len + digit - 11]);
            std.debug.assert(line[i] >= '0' and line[i] <= '9');
            to_add = 10 * to_add + line[i] - '0';
            i += 1;
        }
        total_joltage += to_add;
    }
    return total_joltage;
}

…formatting that spoiler felt very order sensitive

1 Like

Finally find some time for day 3. And it took me quite a while to figure out the correct search window on part 2.

Part 1

fn part1(allocator: Allocator) anyerror!void {
    _ = allocator;
    const input_embed = @embedFile("puzzle-03");
    var sum: u32 = 0;

    var reader: std.Io.Reader = .fixed(input_embed);
    while (try reader.takeDelimiter('\n')) |line| {
        var largest: u8 = 0;

        for (0..line.len) |i| {
            const start_digit = try std.fmt.charToDigit(line[i], 10);
            for (i + 1..line.len) |j| {
                const end_digit = try std.fmt.charToDigit(line[j], 10);
                const combination = start_digit * 10 + end_digit;
                if (combination > largest) largest = combination;
            }
        }
        sum += largest;
    }
    std.debug.print("Result: {d}\n", .{sum});
}

Part 2

fn part2(allocator: Allocator) anyerror!void {
    _ = allocator;
    var total_joltage: u128 = 0;

    const input = @embedFile("puzzle-03");
    var reader: std.Io.Reader = .fixed(input);

    const battery_count = 12;
    while (try reader.takeDelimiter('\n')) |line| {
        const contained_number = try findLargestContainedNumber(
            @TypeOf(total_joltage),
            line,
            battery_count,
        );
        total_joltage += contained_number;
    }

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

fn findLargestContainedNumber(comptime T: type, line: []const u8, battery_count: usize) !T {
    var largest_contained_number: T = 0;
    var start_idx: usize = 0;
    var search_space = line[start_idx .. line.len - (battery_count - 1)];
    var found_digit_count: usize = 0;
    for (0..battery_count) |e| {
        var max: u8 = try std.fmt.charToDigit(search_space[0], 10);
        var max_idx: usize = 0;
        for (search_space[0..], 0..) |c, i| {
            if (try std.fmt.charToDigit(c, 10) > max) {
                max = c - '0';
                max_idx = i;
            }
        }

        largest_contained_number += max * std.math.pow(T, 10, (battery_count - 1) - e);

        found_digit_count += 1;
        start_idx += max_idx + 1;
        const upper_bound = @min(
            line.len,
            @max(
                (line.len - (battery_count -| 1 -| found_digit_count)),
                start_idx + 1,
            ),
        );
        search_space = line[start_idx..upper_bound];
        // std.debug.print("   > max: {d} [{d}]\n", .{ max, max_idx });
    }
    return largest_contained_number;
}