AoC 2025: Day 1

Main thread for Day 1 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.
  2. Have Fun

Day 1 Challenge

Templates:

Resources:

9 Likes

It took me a while to figure out all the edge cases.

var dial: isize = 50;
var counter: usize = 0;
var x434C49434B: usize = 0;
while (try reader.takeDelimiter('\n')) |line| {
    const clicks = switch (line[0]) {
        'R' => try std.fmt.parseInt(isize, line[1..], 10),
        'L' => -try std.fmt.parseInt(isize, line[1..], 10),
        else => return error.InvalidCharacter,
    };
    if (clicks == 0) return error.ZeroClicks;
    x434C49434B += @abs(@divFloor(dial + clicks, 100));
    if (dial == 0 and clicks < 0) x434C49434B -= 1;
    dial = @mod(dial + clicks, 100);
    if (dial == 0 and clicks < 0) x434C49434B += 1;
    if (dial == 0) counter += 1;
}
try writer.print("{}\n{}\n", .{ counter, x434C49434B });
try writer.flush();
2 Likes

Also, quick note to avoid strange variable names: 0x434C49434B in ASCII is CLICK.

4 Likes

As is usually the case, not much to say really about Day 1; you just have to understand what’s being asked, take advantage of the test cases and work through the problem. Don’t forget modular aritthmetic .

Hi! This is my attempt to learn Zig while doing Advent of Code. Previously I am a webdev with TS, and mobile dev with Flutter. I am continuing my study in CS in which I am learning C and C++, but Zig took my interest and I want to know more.

This is my attempt, which resulting in correct answer:

// zig 0.15.2
const std = @import("std");

pub fn main() !void {
    var total_zeroes: i32 = 0;
    var current_dial: i32 = 50;
    const fname = "day-01.txt";

    // begin code I got from consulting with AI
    const f = try std.fs.cwd().openFile(fname, .{});
    defer f.close();

    var read_buf: [21000]u8 = undefined;
    var start_index: usize = 0;
    var end_index: usize = 0;

    while (true) {
        if (start_index == end_index) {
            start_index = 0;
            end_index = try f.read(&read_buf);
            if (end_index == 0) break;
        }

        const slice = read_buf[start_index..end_index];
        if (std.mem.indexOfScalar(u8, slice, '\n')) |newline_pos| {
            const line = slice[0..newline_pos];
            // std.debug.print("Line: {s}\n", .{line});

            current_dial = nextDialState(current_dial, line);
            if (current_dial == 0) total_zeroes += 1;

            start_index += newline_pos + 1;
        } else {
            // To handle if line is longer
            // std.debug.print("Partial/Long Line: {s}", .{slice});
            current_dial = nextDialState(current_dial, slice);
            if (current_dial == 0) total_zeroes += 1;
            start_index = end_index;
        }
    }
    // end code I got from consulting with AI

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

fn nextDialState(curr: i32, action: []const u8) i32 {
    const direction = action[0];
    const modifier_string = action[1..];
    const modifier_int = std.fmt.parseInt(i32, modifier_string, 10) catch unreachable;
    // return switch (direction) {
    //     'L' => {
    //         const temp_result = curr - modifier_int;
    //         return if (temp_result < 0) temp_result + 100 else temp_result;
    //     },
    //     'R' => {
    //         const temp_result = curr + modifier_int;
    //         return if (temp_result > 99) temp_result - 100 else temp_result;
    //     },
    //     else => curr,
    // };

    // there is possibility that the dial rotated more than one revolution in each action
    var result = switch (direction) {
        'L' => curr - modifier_int,
        'R' => curr + modifier_int,
        else => curr,
    };
    while (result < 0 or result > 99) {
        result = if (result < 0) result + 100 else result - 100;
    }
    return result;
}


// pub fn main() !void {
//     const test_string = "L32";
//     const split_string_front = test_string[0..1];
//     const split_string_back = test_string[1..];
//     std.debug.print("#01: {s}\n", .{test_string});
//     std.debug.print("#02: {s}\n", .{split_string_front});
//     std.debug.print("#03: {s}\n", .{split_string_back});
//     std.debug.print("#04: {s}\n", .{test_string});
// }

I have difficulties in taking from file and reading line-by-line, a little different from C++’s approach. So I consulted with Gemini AI for that. Though I also still having difficulties because apparently, Gemini doesn’t know Zig version 0.15.2.

Please criticize my Zig approach and I can manage brutally honest opinion :smiley:

1 Like

Was mainly working on refactoring my advent of code framework yesterday.
Didn’t have too much mental capacity yesterday left, so my solution is just stupidly iterating through the rotations, counting how often it sees a 0. Not the most elegant solution but it yielded the correct solution.

var row_it = std.mem.tokenizeSequence(u8, @embedFile("puzzle"), "\n");
const max = 100; // 0 - 99 = 100
var dial: i32 = 50;
var result: u32 = 0;
while (row_it.next()) |row| {
    // parse 'L' as negative rotation
    const sign: i32 = if (row[0] == 'L') -1 else 1;
    const number = try std.fmt.parseInt(i32, row[1..], 10);
    var change = sign * number;
    while (change != 0) : (change += -1 * sign) {
        dial += sign;
        dial = @mod(dial, max);
        if (dial == 0) result += 1;
    }
}
std.debug.print("\nResult: {d}", .{result});
1 Like

Here’s the core of my solution for part 2. I like my edge case handling:

var addr: i32 = 50;
var zero_count: usize = 0;

fn procesLine(num: i32) !void {
    if (num < 0) {
        // count crossings over the 0/1 boundary
        const start = @divFloor(addr - 1, 100);
        addr += num;
        const end = @divFloor(addr - 1, 100);
        zero_count += @abs(end - start);
    } else {
        // count crossings over the 99/100 boundary
        const start = @divFloor(addr, 100);
        addr += num;
        const end = @divFloor(addr, 100);
        zero_count += @abs(end - start);
    }
}
3 Likes
Part1
const std = @import(“std”);

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 nzeros: usize = 0;
var position: i32 = 50;
var lines = std.mem.tokenizeScalar(u8, input, '\n');
while (lines.next()) |line| {
    const dir = line[0];
    const number: i32 = try std.fmt.parseInt(i32, line[1..], 10);

    switch (dir) {
        'L' => {
            position -= number;
        },
        'R' => {
            position += number;
        },
        else => unreachable,
    }
    position = @mod(position, 100);
    //std.debug.print("pos: {d}\n", .{position});
    if (position == 0) {
        nzeros += 1;
    }
}

try stdout.print("Number of zeroes during dial turning: {d}\n", .{nzeros});
try stdout.flush();
}
Part2
const std = @import(“std”);

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 nzeros: usize = 0;
var position: i32 = 50;
var lines = std.mem.tokenizeScalar(u8, input, '\n');
while (lines.next()) |line| {
    const dir = line[0];
    const number: usize = try std.fmt.parseInt(usize, line[1..], 10);

    const div = @divFloor(number, 100);
    nzeros += div;
    const rest: i32 = @intCast(@mod(number, 100));
    switch (dir) {
        'L' => {
            const diff = position - rest;
            if (position == 0) {
                if (diff >= 0) {
                    position = diff;
                } else {
                    position = 100 + diff;
                }
            } else {
                if (diff == 0) {
                    position = 0;
                    nzeros += 1;
                } else if (diff > 0) {
                    position = diff;
                } else if (diff < 0) {
                    position = 100 + diff;
                    nzeros += 1;
                }
            }
        },
        'R' => {
            const sum: i32 = position + rest;
            if (sum < 100) {
                position = sum;
            } else {
                position = sum - 100;
                nzeros += 1;
            }
        },
        else => unreachable,
    }
    //std.debug.print("instr: {s}, pos: {d}, zeroes {d}\n", .{ line, position, nzeros });
}

try stdout.print("Number of zeroes during dial turning: {d}\n", .{nzeros});
try stdout.flush();
}

A branchless version.

const std = @import("std");

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

    var part1: usize = 0;
    var part2: usize = 0;
    var dial: isize = 50;

    var i: usize = 0;
    while (i + 1 < input.len) {
        const l = input[i..][0..];
        const d2 = l[3] == '\n';
        const d3 = l[4] == '\n';
        const m2: usize = @intFromBool(d2 or d3);
        const m3: usize = @intFromBool(d3);

        var n: usize = l[1] -| '0';
        n = (9 * m2 + 1) * n + m2 * (l[2] -| '0');
        n = (9 * m3 + 1) * n + m3 * (l[3] -| '0');

        const rot: isize = (l[0] >> 1) & 1;
        const delta = @as(isize, @intCast(n)) * (1 - (2 * rot));
        const diff = @divTrunc((dial + delta) + (rot * -100), 100);

        part2 += @abs(diff);
        part2 -= @intFromBool(delta < 0 and dial == 0);
        dial = @mod(dial + delta, 100);
        part1 += @intFromBool(dial == 0);
        i += 3 + m2 + m3;
    }

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

“Comptime is just normal Zig, they said…”

comptime var answer: struct { crossings: u32, zeroes: u32 } = undefined;

comptime {
    const input = @embedFile("input/01.txt");

    // An omen of evil shenanigans
    @setEvalBranchQuota(std.math.maxInt(u32));

    // Count the amount of rotations in the input
    var count = 0;
    for (input) |byte| {
        if (byte == '\n') {
            count += 1;
        }
    }

    // Parse the input into a list of rotations
    var rotations: [count]comptime_int = undefined;
    {
        var i = 0;
        for (&rotations) |*rotation| {
            const prefix: u8 = input[i];

            const remaining = input[i + 1 ..];
            const token_end = std.mem.indexOfScalar(u8, remaining, '\n') orelse remaining.len;

            // windows pls
            const number = std.mem.trim(u8, remaining[0..token_end], "\r\n");

            rotation.* = (if (prefix == 'L') -1 else 1) * (std.fmt.parseInt(i32, number, 10) catch unreachable);

            // 1 for the prefix L/R, 1 for the newline
            i += token_end + 2;
        }
        std.debug.assert(input.len == i);
    }

    // Compute the answer
    var zeroes = 0;
    var crossings = 0;
    var position = 50;
    for (rotations) |rotation| {
        crossings += @abs(@divFloor(position + rotation, 100));
        const new_position = @mod(position + rotation, 100);
        if (new_position == 0) zeroes += 1;

        // starting on zero gets counted, shouldn't
        crossings += if (rotation < 0 and position == 0) -1 else 0;

        // ending on zero doesn't get counted, should
        crossings += if (rotation < 0 and new_position == 0) 1 else 0;

        position = new_position;
    }

    answer = .{ .crossings = crossings, .zeroes = zeroes };
}
1 Like