🍯 Glaze

const std = @import("std");
const builtin = @import("builtin");

fn readInputFile(allocator: std.mem.Allocator, filename: []const u8) anyerror![]u32 {
    var result = std.ArrayList(u32).init(allocator);

    const file = try std.fs.cwd().openFile(filename, .{ .read = true });
    defer file.close();

    const reader = file.reader();
    while (true) {
        var line: []u8 = undefined;
        if (builtin.os.tag == .windows) {
            // NOTE: Read another byte on windows due to two-byte eol.
            line = reader.readUntilDelimiterAlloc(allocator, '\r', 16) catch break;
            _ = try reader.readByte();
        } else {
            line = reader.readUntilDelimiterAlloc(allocator, '\n', 16) catch break;
        }
        defer allocator.free(line);

        var message: u32 = 0;
        for (line) |char| {
            message |= @as(u32, if (char == '1') 0x1 else 0x0);
            message <<= 1;
        }
        message >>= 1;
        try result.append(message);
    }

    return result.items;
}

pub fn task1(comptime T: type, diagnostic: []const u32) u32 {
    var gamma_rate: u32 = 0;
    var epsilon_rate: u32 = 0;

    var bit: u5 = 1;
    while (bit <= @bitSizeOf(T)) : (bit += 1) {
        var on: u32 = 0;
        var off: u32 = 0;

        var message: u32 = 0;
        while (message < diagnostic.len) : (message += 1) {
            const bit_val = (diagnostic[message] >> (@bitSizeOf(T) - bit)) & 0x1;
            if (bit_val == 1) {
                on += 1;
            } else {
                off += 1;
            }
        }

        gamma_rate |= @as(u32, if (on > off) 1 else 0);
        gamma_rate <<= 1;
        epsilon_rate |= @as(u32, if (on < off) 1 else 0);
        epsilon_rate <<= 1;
    }
    gamma_rate >>= 1;
    epsilon_rate >>= 1;

    std.log.debug("gamma_rate {}", .{gamma_rate});
    std.log.debug("epsilon_rate {}", .{epsilon_rate});

    return gamma_rate * epsilon_rate;
}

fn pickDiagnostics(comptime T: type, allocator: std.mem.Allocator, bit: u5, invert: bool, diagnostic: []const u32) []const u32 {
    var result = std.ArrayList(u32).init(allocator);

    var on: u32 = 0;
    var off: u32 = 0;

    var message: u32 = 0;
    while (message < diagnostic.len) : (message += 1) {
        const bit_val = (diagnostic[message] >> (@bitSizeOf(T) - bit)) & 0x1;
        if (bit_val == 1) {
            on += 1;
        } else {
            off += 1;
        }
    }

    message = 0;
    while (message < diagnostic.len) : (message += 1) {
        var bit_val = (diagnostic[message] >> (@bitSizeOf(T) - bit)) & 0x1;
        bit_val = if (invert) (~bit_val) & 0x1 else bit_val;
        if ((on >= off and bit_val == 1) or (on < off and bit_val == 0)) {
            result.append(diagnostic[message]) catch unreachable;
        }
    }

    return result.items;
}

pub fn task2(comptime T: type, diagnostic: []const u32) u32 {
    var buffer: [65536]u8 = undefined;
    const allocator = std.heap.FixedBufferAllocator.init(&buffer).allocator();

    var oxygen_rating: u32 = 0;
    var co2_rating: u32 = 0;

    var filtered_oxygen: []const u32 = allocator.dupe(u32, diagnostic) catch unreachable;
    var filtered_co2: []const u32 = allocator.dupe(u32, diagnostic) catch unreachable;
    var bit: u5 = 1;
    while (bit <= @bitSizeOf(T)) : (bit += 1) {
        if (filtered_oxygen.len > 1) {
            var prev = filtered_oxygen;
            filtered_oxygen = pickDiagnostics(T, allocator, bit, false, prev);
            allocator.free(prev);
        }
        if (filtered_co2.len > 1) {
            var prev = filtered_co2;
            filtered_co2 = pickDiagnostics(T, allocator, bit, true, prev);
            allocator.free(prev);
        }
    }
    oxygen_rating = filtered_oxygen[0];
    co2_rating = filtered_co2[0];

    std.log.debug("oxygen_rating {}", .{oxygen_rating});
    std.log.debug("co2_rating {}", .{co2_rating});

    return oxygen_rating * co2_rating;
}

pub fn main() anyerror!void {
    var buffer: [65536]u8 = undefined;
    var fixed_buffer = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fixed_buffer.allocator();

    const input = try readInputFile(allocator, "input.txt");

    const task_1_result = task1(u12, input);
    std.log.info("Task 1 result: {}", .{task_1_result});

    const task_2_result = task2(u12, input);
    std.log.info("Task 2 result: {}", .{task_2_result});
}