AoC 2025: Day 7

Main thread for Day 7 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 7 Challenge

Templates:

Resources:

1 Like

The first part is quite straight forward. The logic behind the second part is to track how many paths are shared, and then sum them when they reach the end.

const std = @import("std");
const data = @embedFile("data");

const columns = blk: {
    var count: usize = 0;
    @setEvalBranchQuota(90000);
    for (data) |d| {
        if (d == '\n') break;
        count += 1;
    }
    break :blk count;
};

pub fn main() !void {
    // part1

    var result: usize = 0;

    var it = std.mem.tokenizeAny(u8, data, "\n");
    var first_line = it.next().?;

    var streams: [columns]usize = @splat(0);
    for (0..first_line.len) |i| {
        if (first_line[i] == 'S') streams[i] = 1;
    }
    // skip empty line
    _ = it.next();

    while (it.next()) |line| {
        for (0..line.len) |c| {
            if (line[c] != '.') if (streams[c] == 1) {
                streams[c - 1] = 1;
                streams[c] = 0;
                streams[c + 1] = 1;
                result += 1;
            };
        }
        // skip every second line
        _ = it.next();
    }

    // part2

    var result2: usize = 0;

    it.reset();
    first_line = it.next().?;
    @memset(&streams, 0);
    for (0..first_line.len) |i| {
        if (first_line[i] == 'S') streams[i] = 1;
    }
    // skip empty line
    _ = it.next();

    while (it.next()) |line| {
        for (0..line.len) |c| {
            if (line[c] != '.') if (streams[c] != 0) {
                // if already 1 then add 1
                if (streams[c - 1] == 0) {
                    streams[c - 1] = streams[c];
                } else {
                    streams[c - 1] += streams[c];
                }
                // if already 1 then add 1
                if (streams[c + 1] == 0) {
                    streams[c + 1] = streams[c];
                } else {
                    streams[c + 1] += streams[c];
                }
                streams[c] = 0;
            };
        }
        // skip every second line
        _ = it.next();
    }

    // add the num of possible paths to the i-th position
    for (0..streams.len) |i| {
        result2 += streams[i];
    }

    std.debug.print("Result is {}\n", .{result});
    std.debug.print("Result2 is {}\n", .{result2});
}

1 Like
pub fn part2(input: []const u8) !usize {
    var line_it = std.mem.splitScalar(u8, input, '\n');
    const num_col = 141;
    var beam_timelines: [num_col]usize = @splat(0);
    const first_line = line_it.next().?;
    beam_timelines[std.mem.findScalar(u8, first_line, 'S').?] = 1;
    while (line_it.next()) |line| {
        if (line.len != num_col) break;
        var current_col: usize = 0;
        while (std.mem.indexOfScalarPos(u8, line, current_col, '^')) |splitter_pos| {
            defer current_col = splitter_pos + 1;
            if (beam_timelines[splitter_pos] != 0) {
                beam_timelines[splitter_pos - 1] += beam_timelines[splitter_pos];
                beam_timelines[splitter_pos + 1] += beam_timelines[splitter_pos];
                beam_timelines[splitter_pos] = 0;
            }
        }
    }
    var sum: usize = 0;
    for (beam_timelines) |timeline| {
        sum += timeline;
    }
    return sum;
}

Algorithm Hint

#1 use BFS
- queue item: (x, y)
#2 use Memoized recursion
- key: (x, y), value: timeline count

Really enjoyed today, probably because I already struggled on similar puzzles in previous AoC years!

Another fun AOC day. repo.

Day 7, part 1:
  - Took 31.119us on average over 10000 runs
Day 7, part 2:
  - Took 27.382us on average over 10000 runs

I did very similar things for part 1 and 2, however with part 1 I used a bool array and with part 2 u64 array and the counting method differs slightly. Part 2:

fn solution(r: *Reader, _: Io, gpa: Allocator) !u64 {
    const first_line = try r.takeDelimiter('\n') orelse fatal("Need first line", .{});
    _ = r.discardDelimiterInclusive('\n') catch {}; // discard empty line

    const line_len = first_line.len;

    const full_buf = try gpa.alloc(u64, line_len * 2);
    defer gpa.free(full_buf);

    @memset(full_buf, 0);

    var prev_line: []u64 = full_buf[0..line_len];
    var this_line: []u64 = full_buf[line_len..];
    assert(prev_line.len == line_len);
    assert(this_line.len == line_len);

    prev_line[mem.findScalar(u8, first_line, 'S').?] = 1;

    while (try r.takeDelimiter('\n')) |line| {
        assert(line.len == line_len);
        defer {
            _ = r.discardDelimiterInclusive('\n') catch {}; // discard empty line
            mem.swap([]u64, &prev_line, &this_line);
            @memset(this_line, 0);
        }

        for (prev_line, 0..) |value, i| {
            if (value == 0) continue;

            switch (line[i]) {
                '.' => this_line[i] += value,
                '^' => {
                    if (i != 0 and line[i - 1] == '.') {
                        this_line[i - 1] += value;
                    }
                    if (i != line_len - 1 and line[i + 1] == '.') {
                        this_line[i + 1] += value;
                    }
                },
                else => |byte| fatal("Invalid character '{c}'", .{byte}),
            }
        }
    }

    var worlds: u64 = 0;
    for (prev_line) |value| worlds += value;
    return worlds;
}

Can someone explain the task, please?

In this example, a tachyon beam is split a total of 21 times.

How is split defined? My logic is each splitter splits the beam, so in the example there are 22 splitters which means it should have been 22 splits?

https://zigbin.io/bb55d4
50 lines, 2 allocations. main() total runtime is around 80us on my machine.

1 Like

Adopted a Map data structure I’ve used on previous years. Might not be the shortest code, but allows to create a nice visualization of what’s going on (video gif is a 10 x speed up).

So far only solved…
…Part 1:

aoc-2025-day07-part1

const Map = struct {
    rows: usize,
    cols: usize,
    buffer: []u8,

    pub fn init(allocator: Allocator, buffer: []const u8) !Map {
        if (std.mem.indexOf(u8, buffer, "\n")) |line_end| {
            const clean = std.mem.trimEnd(u8, buffer, "\n");
            return .{
                .buffer = try std.mem.replaceOwned(u8, allocator, clean, "\n", ""),
                .cols = line_end,
                .rows = (clean.len - 1) / line_end,
            };
        }
        @panic("Failed to split input buffer!");
    }

    pub fn get(self: *const Map, x: usize, y: usize) u8 {
        return self.buffer[(y * self.cols) + x];
    }

    pub fn set(self: *const Map, x: usize, y: usize, v: u8) void {
        self.buffer[(y * self.cols) + x] = v;
    }

    pub fn format(self: Map, writer: *std.Io.Writer) std.Io.Writer.Error!void {
        try writer.print("Map {d} x {d}\n", .{ self.rows, self.cols });
        for (0..self.rows) |y| {
            for (0..self.cols) |x| {
                try writer.print("{c}", .{self.get(x, y)});
            }
            try writer.print("\n", .{});
        }
    }

    pub fn deinit(self: Map, allocator: Allocator) void {
        allocator.free(self.buffer);
    }

    pub fn animate(self: Map) void {
        std.debug.print(t.hide_cursor, .{});
        for (0..self.cols) |x| {
            for (0..self.rows - 1) |y| {
                std.debug.print(t.yx, .{ y, x });
                const v = self.get(x, y);
                switch (v) {
                    '|' => std.debug.print("{s}{c}{s}", .{ t.yellow, v, t.clear }),
                    '^' => std.debug.print("{s}{c}{s}", .{ t.red, v, t.clear }),
                    '.' => std.debug.print("{s}{c}{s}", .{ t.dark_gray, v, t.clear }),
                    else => std.debug.print("{s}{c}{s}", .{ t.blue, v, t.clear }),
                }
            }
        }
    }
};

fn part1(allocator: Allocator) anyerror!void {
    const input = @embedFile("puzzle-07");
    // std.debug.print("--- INPUT---\n{s}\n------------\n", .{input});

    const map: Map = try .init(allocator, input);

    var start: @Vector(2, usize) = @splat(0);
    var splitters: std.array_list.Managed(@Vector(2, usize)) = .init(allocator);

    // std.debug.print("{d} x {d}\n", .{ map.cols, map.rows });

    for (0..map.rows) |y| {
        for (0..map.cols) |x| {
            switch (map.get(x, y)) {
                'S' => start = .{ x, y },
                '^' => try splitters.append(.{ x, y }),
                else => continue,
            }
        }
    }

    var beams: std.array_list.Managed(@Vector(2, usize)) = .init(allocator);
    try beams.append(start);
    var splits: usize = 0;
    while (beams.pop()) |b| {
        // not necessary, doing this just for achieving a nicer order in the animation
        std.mem.sort(@Vector(2, usize), beams.items, {}, comptime struct {
            pub fn f(_: void, beam1: @Vector(2, usize), beam2: @Vector(2, usize)) bool {
                return beam1[1] > beam2[1];
            }
        }.f);

        map.animate();
        const curr: @Vector(2, usize) = .{ b[0], b[1] };
        if (curr[0] < 0 or
            curr[0] > map.cols - 1 or
            curr[1] > map.rows - 1)
        {
            continue;
        }
        switch (map.get(curr[0], curr[1])) {
            'S' => try beams.append(.{ curr[0], curr[1] + 1 }),
            '.' => try beams.append(.{ curr[0], curr[1] + 1 }),
            '^' => {
                splits += 1;
                if (map.get(curr[0] - 1, curr[1]) != '|')
                    try beams.append(.{ curr[0] - 1, curr[1] });
                if (map.get(curr[0] + 1, curr[1]) != '|')
                    try beams.append(.{ curr[0] + 1, curr[1] });
            },
            else => continue,
        }
        map.set(curr[0], curr[1], '|');
    }
    // std.debug.print("{f}\n", .{map});

    std.debug.print("# Splitters: {d}\n", .{splitters.items.len});
    std.debug.print("Result: {d}\n", .{splits});
}
2 Likes