AoC 2024: Day 24

Happy Christmas Eve!

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

Templates:

Resources:

Previous days discussions

Day 1: AoC 2024: Day 1
Day 2: AoC 2024: Day 2
Day 3: AoC 2024: Day 3
Day 4: AoC 2024: Day 4
Day 5: AoC 2024: Day 5
Day 6: AoC 2024: Day 6
Day 7: AoC 2024: Day 7
Day 8: AoC 2024: Day 8
Day 9: AoC 2024: Day 9
Day 10: AoC 2024: Day 10
Day 11: AoC 2024: Day 11
Day 12: AoC 2024: Day 12
Day 13: AoC 2024: Day 13
Day 14: AoC 2023: Day 14
Day 15: AoC 2024: Day 15
Day 16: AoC 2024: Day 16
Day 17: AoC 2024: Day 17
Day 18: AoC 2024: Day 18
Day 19: AoC 2024: Day 19
Day 20: AoC 2024: Day 20
Day 21: AoC 2024: Day 21
Day 22: AoC 2024: Day 22
Day 23: AoC 2024: Day 23

1 Like

Part 2 was a very visual & manual task - before coming up with heuristics, I wrote a simulator and solved it manually. (Video of the simulator in action below).
Automated solution is 100% heuristics, but super quick.

Source: aoc2024/src/day24.zig at main · p88h/aoc2024 · GitHub
Video: https://www.youtube.com/watch?v=nWNtNvxCjp0

Benchmark:

        parse   part1   part2   total
day 24:  9.0 µs  2.7 µs  0.8 µs 12.6 µs (+-4%) iter=98110

Hi all,

a little bit late, but Family and stuff. Also I challenged myself to solve this puzzle completely in comptime. During runtime only the result should be printed. I finally did it (for part 1):

Solution 1 (comptime only)
const std = @import("std");

//const input = @embedFile("./small.inp");
//const input = @embedFile("./small2.inp");
//const input = @embedFile("./medium.inp");
const input = @embedFile("./big.inp");

const maxBranch = 10_000_000;
// Wires
const emptyStr = "   ";
const maxWires = 1000;

fn getWires(comptime inp: []const u8) [maxWires][3:0]u8 {
    @setEvalBranchQuota(maxBranch);
    var list: [maxWires][3:0]u8 = undefined;
    for (&list) |*elem| {
        elem[0] = emptyStr[0];
        elem[1] = emptyStr[1];
        elem[2] = emptyStr[2];
    }
    var lines = std.mem.tokenizeScalar(u8, inp, '\n');
    var idx: usize = 0;
    while (lines.next()) |line| {
        var tmplist: [3][]const u8 = undefined;
        var app = false;
        if (line.len > 10 and //
            (std.mem.eql(u8, line[4..7], "XOR") or //
            std.mem.eql(u8, line[4..7], "AND")))
        {
            app = true;
            tmplist[0] = line[0..3];
            tmplist[1] = line[8..11];
            tmplist[2] = line[15..18];
        } else if (std.mem.eql(u8, line[4..6], "OR")) {
            app = true;
            tmplist[0] = line[0..3];
            tmplist[1] = line[7..10];
            tmplist[2] = line[14..17];
        }
        if (app) {
            for (tmplist) |str| {
                var inlist = false;
                for (list) |elem| {
                    if (std.mem.eql(u8, &elem, str)) {
                        inlist = true;
                        break;
                    }
                }
                if (!inlist) {
                    list[idx][0] = str[0];
                    list[idx][1] = str[1];
                    list[idx][2] = str[2];
                    idx += 1;
                }
            }
        }
    }
    return list;
}

pub fn createWiresType() type {
    @setEvalBranchQuota(maxBranch);
    const wires = comptime getWires(input);
    comptime var nfields = 0;
    for (wires) |wire| {
        if (!std.mem.eql(u8, &wire, emptyStr)) {
            nfields += 1;
        } else {
            break;
        }
    }

    var fields: [nfields]std.builtin.Type.StructField = undefined;
    for (wires[0..nfields], 0..) |wire, idx| {
        fields[idx] = .{ //
            .name = &wire,
            .type = i8,
            .default_value = null,
            .is_comptime = false,
            .alignment = 0,
        };
    }

    return @Type(.{
        .Struct = .{
            .layout = .auto,
            .fields = &fields,
            .decls = &.{},
            .is_tuple = false,
            .backing_integer = null,
        },
    });
}

pub fn initWires(comptime inp: []const u8, comptime wires: *Wires_t) void {
    @setEvalBranchQuota(maxBranch);
    comptime {
        for (@typeInfo(Wires_t).Struct.fields) |field| {
            @field(wires, field.name) = 0;
        }
    }
    var blocks = std.mem.tokenizeSequence(u8, inp, "\n\n");
    var lines = std.mem.tokenizeScalar(u8, blocks.next().?, '\n');

    while (lines.next()) |line| {
        const initval = if (line[5] == '1') 1 else 0;
        @field(wires, line[0..3]) = initval;
    }
}

const Wires_t = createWiresType();

pub fn wireOutput(comptime wires: Wires_t) usize {
    @setEvalBranchQuota(maxBranch);
    var output: usize = 0;
    comptime {
        for (@typeInfo(Wires_t).Struct.fields) |field| {
            if (field.name[0] == 'z') {
                const val: usize = @field(wires, field.name);
                const idx: u6 = 10 * (field.name[1] - '0') + field.name[2] - '0';
                output += val << idx;
            }
        }
    }
    return output;
}

pub fn printWires(wires: Wires_t) void {
    @setEvalBranchQuota(maxBranch);
    inline for (comptime @typeInfo(Wires_t).Struct.fields) |field| {
        std.debug.print("{s}: {b}\n", .{ field.name, @field(wires, field.name) });
    }
}

// Gates
const maxGates = maxWires;
const ops = enum { and_op, or_op, xor_op };

const Gate_t = struct {
    inp1: []const u8,
    inp2: []const u8,
    out: []const u8,
    op: ops,
};

const Gates_t = struct {
    ngates: usize,
    gates: [maxGates]Gate_t,
};

pub fn initGates(inp: []const u8, gates: *Gates_t) void {
    @setEvalBranchQuota(maxBranch);
    gates.ngates = 0;

    var blocks = std.mem.tokenizeSequence(u8, inp, "\n\n");
    _ = blocks.next();
    var lines = std.mem.tokenizeScalar(u8, blocks.next().?, '\n');

    while (lines.next()) |line| {
        var field = std.mem.tokenizeScalar(u8, line, ' ');
        const inp1 = field.next().?;
        const opstr = field.next().?;
        const inp2 = field.next().?;
        _ = field.next();
        const out = field.next().?;
        var op: ops = undefined;
        if (std.mem.eql(u8, opstr, "AND")) {
            op = .and_op;
        } else if (std.mem.eql(u8, opstr, "OR")) {
            op = .or_op;
        } else if (std.mem.eql(u8, opstr, "XOR")) {
            op = .xor_op;
        }

        gates.gates[gates.ngates] = Gate_t{
            .inp1 = inp1,
            .inp2 = inp2,
            .op = op,
            .out = out,
        };
        gates.ngates += 1;
    }
}

pub fn printGates(gates: Gates_t) void {
    @setEvalBranchQuota(maxBranch);
    for (0..gates.ngates) |i| {
        const gate = gates.gates[i];
        const opstr = switch (gate.op) {
            .and_op => "AND",
            .or_op => "OR ",
            .xor_op => "XOR",
        };
        std.debug.print("{s} {s} {s} -> {s}\n", //
            .{ gate.inp1, opstr, gate.inp2, gate.out });
    }
}

pub fn computeWire(comptime wires: *Wires_t, comptime gates: Gates_t, out: []const u8) void {
    @setEvalBranchQuota(maxBranch);
    if (out[0] == 'x') {
        return;
    }
    for (0..gates.ngates) |i| {
        const gate = gates.gates[i];
        if (std.mem.eql(u8, gate.out[0..3], out[0..3])) {
            computeWire(wires, gates, gate.inp1);
            computeWire(wires, gates, gate.inp2);
            switch (gate.op) {
                .and_op => { //
                    @field(wires, gate.out) |= //
                        @field(wires, gate.inp1) //
                        & //
                        @field(wires, gate.inp2);
                },
                .or_op => {
                    @field(wires, gate.out) |= //
                        @field(wires, gate.inp1) //
                        | //
                        @field(wires, gate.inp2);
                },
                .xor_op => {
                    @field(wires, gate.out) |= //
                        @field(wires, gate.inp1) //
                        ^ //
                        @field(wires, gate.inp2);
                },
            }
        }
    }
}

pub fn computeCircuit(comptime wires: *Wires_t, comptime gates: Gates_t) void {
    @setEvalBranchQuota(maxBranch);
    for (0..gates.ngates) |i| {
        const gate = gates.gates[i];
        if (gate.out[0] == 'z') {
            computeWire(wires, gates, gate.out);
        }
    }
}

pub fn main() !void {
    @setEvalBranchQuota(maxBranch);
    comptime var wires: Wires_t = undefined;
    comptime initWires(input, &wires);
    comptime var gates: Gates_t = undefined;
    comptime initGates(input, &gates);

    //printWires(wires);
    //printGates(gates);
    //std.debug.print("\n", .{});
    //printGates(gates);
    comptime computeCircuit(&wires, gates);
    //printWires(wires);
    const output = comptime wireOutput(wires);
    std.debug.print("Output wire signals are: {b} => {d}\n", .{ output, output });
}
Solution 2 (TODO)
//TODO

Edit: Timings

Benchmark 1: ./solution-1
  Time (mean ± σ):       0.0 µs ±   1.0 µs    [User: 104.4 µs, System: 2.1 µs]
  Range (min … max):     0.0 µs …  52.6 µs    2808 runs
2 Likes