AoC 2025: Day 2

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

Templates:

Resources:

1 Like

Algorithm hint #2

  1. Determine the minimum id and the maximum id in all range.
  2. Generate repeated ids from the minimum id to the maximum id.
    1. Make sure to sort and deduplicate them.
  3. For each range, use binary search on the repeated ids with both the range’s lower and upper values.
  4. Sum the repeated ids that fall within each the original range.
1 Like
Part1
const std = @import("std");

pub fn ndigits(number: usize) usize {
    if (number == 0) {
        return 1;
    }
    const lognumber = std.math.log10_int(number);
    return lognumber + 1;
}

test "ndigits" {
    try std.testing.expectEqual(1, ndigits(0));
    try std.testing.expectEqual(1, ndigits(1));
    try std.testing.expectEqual(1, ndigits(9));
    try std.testing.expectEqual(2, ndigits(10));
    try std.testing.expectEqual(2, ndigits(90));
    try std.testing.expectEqual(3, ndigits(100));
    try std.testing.expectEqual(3, ndigits(999));
    try std.testing.expectEqual(4, ndigits(1000));
    try std.testing.expectEqual(4, ndigits(9999));
}

pub fn validID(id: usize) bool {
    const ndig = ndigits(id);
    if (@mod(ndig, 2) != 0) {
        return true;
    }

    const filter = std.math.powi(usize, 10, ndig / 2) catch {
        unreachable;
    };

    const head = id / filter;
    const tail = @mod(id, filter);
    return head != tail;
}

test "validID" {
    try std.testing.expectEqual(true, validID(1));
    try std.testing.expectEqual(true, validID(111));
    try std.testing.expectEqual(true, validID(11111));

    try std.testing.expectEqual(false, validID(11));
    try std.testing.expectEqual(false, validID(22));
    try std.testing.expectEqual(false, validID(6464));
    try std.testing.expectEqual(false, validID(222222));
    try std.testing.expectEqual(false, validID(1188511885));
    try std.testing.expectEqual(false, validID(38593859));

    try std.testing.expectEqual(true, validID(12));
    try std.testing.expectEqual(true, validID(32));
    try std.testing.expectEqual(true, validID(6364));
    try std.testing.expectEqual(true, validID(212222));
    try std.testing.expectEqual(true, validID(1178511885));
    try std.testing.expectEqual(true, validID(38593858));
}

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 sum: usize = 0;
    var lines = std.mem.tokenizeScalar(u8, input, '\n');
    while (lines.next()) |line| {
        var ranges = std.mem.tokenizeScalar(u8, line, ',');
        while (ranges.next()) |range| {
            var fields = std.mem.tokenizeScalar(u8, range, '-');
            const lower = try std.fmt.parseInt(usize, fields.next().?, 10);
            const upper = try std.fmt.parseInt(usize, fields.next().?, 10);
            for (lower..upper + 1) |id| {
                if (!validID(id)) {
                    //std.debug.print("{s} -> {d}\n", .{ range, id });
                    sum += id;
                }
            }
        }
    }

    try stdout.print("Sum of invalid IDs: {d}\n", .{sum});
    try stdout.flush();
}
Part2
const std = @import("std");

pub fn ndigits(number: usize) usize {
    if (number == 0) {
        return 1;
    }
    const lognumber = std.math.log10_int(number);
    return lognumber + 1;
}

test "ndigits" {
    try std.testing.expectEqual(1, ndigits(0));
    try std.testing.expectEqual(1, ndigits(1));
    try std.testing.expectEqual(1, ndigits(9));
    try std.testing.expectEqual(2, ndigits(10));
    try std.testing.expectEqual(2, ndigits(90));
    try std.testing.expectEqual(3, ndigits(100));
    try std.testing.expectEqual(3, ndigits(999));
    try std.testing.expectEqual(4, ndigits(1000));
    try std.testing.expectEqual(4, ndigits(9999));
}

pub fn validID(id: usize) bool {
    const ndig = ndigits(id);
    if (ndigits(id) == 1) {
        return true;
    }

    //std.log.warn("{d}: len={d}:", .{ id, ndig });
    for (1..ndig / 2 + 1) |tfilterlen| {
        const filterlen = ndig / 2 + 1 - tfilterlen;
        if (@mod(ndig, filterlen) != 0) {
            continue;
        }
        const filter_tail = std.math.powi(usize, 10, filterlen) catch {
            unreachable;
        };
        const tail = @mod(id, filter_tail);
        //std.log.warn("    filterl={d}, tail={d}", .{ filter_tail, tail });
        var valid = false;
        for (2..ndig / filterlen + 1) |igroup| {
            const filter_head = std.math.powi(usize, filter_tail, igroup) catch {
                unreachable;
            };
            const filter_headl = std.math.powi(usize, filter_tail, igroup - 1) catch {
                unreachable;
            };
            const head = @mod(id, filter_head) / filter_headl;
            //std.log.warn("    filterh={d}, head={d}", .{ filter_head, head });
            if (head != tail) {
                valid = true;
            }
        }
        if (!valid) {
            return false;
        }
    }

    return true;
}

test "validID" {
    try std.testing.expectEqual(true, validID(1));
    try std.testing.expectEqual(true, validID(123456));

    try std.testing.expectEqual(false, validID(11));
    try std.testing.expectEqual(false, validID(22));
    try std.testing.expectEqual(false, validID(6464));
    try std.testing.expectEqual(false, validID(222222));
    try std.testing.expectEqual(false, validID(1188511885));
    try std.testing.expectEqual(false, validID(38593859));

    try std.testing.expectEqual(false, validID(12341234));
    try std.testing.expectEqual(false, validID(123123123));
    try std.testing.expectEqual(false, validID(1212121212));
    try std.testing.expectEqual(false, validID(1111111));

    try std.testing.expectEqual(true, validID(12));
    try std.testing.expectEqual(true, validID(32));
    try std.testing.expectEqual(true, validID(6364));
    try std.testing.expectEqual(true, validID(212222));
    try std.testing.expectEqual(true, validID(1178511885));
    try std.testing.expectEqual(true, validID(38593858));

    try std.testing.expectEqual(true, validID(1138138));
}

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 sum: usize = 0;
    var lines = std.mem.tokenizeScalar(u8, input, '\n');
    while (lines.next()) |line| {
        var ranges = std.mem.tokenizeScalar(u8, line, ',');
        while (ranges.next()) |range| {
            var fields = std.mem.tokenizeScalar(u8, range, '-');
            const lower = try std.fmt.parseInt(usize, fields.next().?, 10);
            const upper = try std.fmt.parseInt(usize, fields.next().?, 10);
            for (lower..upper + 1) |id| {
                if (!validID(id)) {
                    //std.debug.print("{s} -> {d}\n", .{ range, id });
                    sum += id;
                }
            }
        }
    }

    try stdout.print("Sum of invalid IDs: {d}\n", .{sum});
    try stdout.flush();
}

here’s one w/out any number formatting https://zigbin.io/0962e4

1 Like

Here’s my brute force implementation using std.mem.window: https://zigbin.io/ca0466

I’m rusty on the language, so I’m sad to say that input sanitation and language stuff took me more time than the solution.
I did solve part 2 before part 1 because I didn’t read the prompt carefully, but it’s a non issue as part 1 is a special case.

I went the buffer route with windowing. Probably more work than necessary. :person_shrugging:

const std = @import("std");

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

    var part1: usize = 0;
    var part2: usize = 0;

    var it = std.mem.tokenizeAny(u8, input, ",\n");
    while (it.next()) |range| {
        const index = std.mem.findScalar(u8, range, '-').?;
        const lo = try std.fmt.parseUnsigned(usize, range[0..index], 10);
        const hi = try std.fmt.parseUnsigned(usize, range[index + 1..], 10);

        const tens = comptime blk: {
            var a: [16]usize = @splat(1);
            for (1..16) |i| a[i] = a[i - 1] * 10;
            break :blk a;
        };

        for (lo..hi + 1) |x| {
            const len = std.math.log10(x) + 1;
            const mask = tens[len / 2];
            part1 += @intFromBool(x / mask == x % mask) * x;

            factors: for (1..len / 2 + 1) |f| {
                if (len % f != 0) continue;
                const rep = x % tens[f];
                var nx = x / tens[f];
                while (nx != 0) : (nx /= tens[f]) {
                    if (nx % tens[f] != rep) continue :factors;
                }
                part2 += x;
                break;
            }
        }
    }

    std.debug.print(
        \\1: {}
        \\2: {}
        \\
        , .{part1, part2});
}

Almost branchless solution
Zig Bin

hint:
you don’t have to check all numbers and sum them up, there’s easily a mathematical formula.
f(L,H) = Σₓ=L→H (10ᵏx + x)
which can be normalize to this:
f(L,H) = (10ᵏ+1)(H−L+1)(H+L)/2

2 Likes

This took me way too long. I wanted to make a solution that didn’t rely on filtering. It should generate the sequence directly. I didn’t quite get there because sequences of repeated digits, repdigits, like 11111 or 4444 show up multiple times, so I skip them conditionally. I also couldn’t come up with a better end condition for when the generated number is greater than the end boundary on the range, so it generates one number over the limit and skips that one too.