Watch
1
0
Fork
You've already forked advent-of-code
0
advent-of-code/2024/src/day17.zig
2024-12-21 21:09:54 -08:00

301 lines
7.4 KiB
Zig

const std = @import("std");
const aoc = @import("aoc.zig");
const Allocator = std.mem.Allocator;
const List = std.ArrayList;
const Map = std.AutoHashMap;
const Set = aoc.AutoHashSet;
const PriorityQueue = std.PriorityQueue;
pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const text = try aoc.readAll(allocator, "input/day17.txt");
defer allocator.free(text);
var device = try Device.parse(allocator, text);
defer device.deinit();
var output = try device.run();
defer output.deinit();
var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
const stdout = bw.writer();
const out = try joinOutput(allocator, output.items);
defer out.deinit();
try stdout.print("{s}\n", .{out.items});
try stdout.print("{d}\n", .{try quine(allocator, device.program)});
try bw.flush();
}
fn joinOutput(allocator: Allocator, nums: []u3) !List(u8) {
var buf = List(u8).init(allocator);
var w = buf.writer();
if (nums.len > 0) {
try w.print("{d}", .{nums[0]});
}
if (nums.len > 1) {
for (nums[1..]) |n| {
try w.print(",{d}", .{n});
}
}
return buf;
}
const Device = struct {
a: u64,
b: u64,
c: u64,
program: List(u3),
ip: usize,
allocator: Allocator,
const Self = @This();
fn parse(allocator: Allocator, text: []const u8) !Self {
var blocks = std.mem.splitSequence(u8, text, "\n\n");
const a, const b, const c = v: {
const block = blocks.next().?;
var lines = std.mem.tokenizeScalar(u8, block, '\n');
var line = lines.next().?;
line = takeLiteral(line, "Register A: ").?;
line, const a = takeInt(line).?;
if (line.len != 0) unreachable;
line = lines.next().?;
line = takeLiteral(line, "Register B: ").?;
line, const b = takeInt(line).?;
if (line.len != 0) unreachable;
line = lines.next().?;
line = takeLiteral(line, "Register C: ").?;
line, const c = takeInt(line).?;
if (line.len != 0) unreachable;
break :v .{ a, b, c };
};
var program = List(u3).init(allocator);
errdefer program.deinit();
{
var line = blocks.next().?;
line = std.mem.trim(u8, line, " \n");
line = takeLiteral(line, "Program: ").?;
var nums = std.mem.tokenizeScalar(u8, line, ',');
while (nums.next()) |num| {
const n = try std.fmt.parseInt(u3, num, 10);
try program.append(n);
}
}
return Self{
.a = a,
.b = b,
.c = c,
.ip = 0,
.program = program,
.allocator = allocator,
};
}
fn deinit(self: *Self) void {
self.program.deinit();
}
fn run(self: *Self) !List(u3) {
var output = List(u3).init(self.allocator);
while (self.ip < self.program.items.len) {
// self.dbg();
self.ip, const out = self.tick();
if (out) |n| {
try output.append(@intCast(n));
}
}
return output;
}
fn dbg(self: Self) void {
std.debug.print("ip: {}\nA: {}\nB: {}\nC: {}\n", .{ self.ip, self.a, self.b, self.c });
std.debug.print("op: {} {}\n", .{
self.program.items[self.ip],
self.program.items[self.ip + 1],
});
const stdin = std.io.getStdIn();
var buf: [1]u8 = undefined;
_ = stdin.reader().readUntilDelimiterOrEof(&buf, '\n') catch return orelse return;
}
fn tick(self: *Self) struct { usize, ?u64 } {
const opcode: Opcode = @enumFromInt(self.program.items[self.ip]);
const operand = self.program.items[self.ip + 1];
const pow = std.math.pow;
switch (opcode) {
.adv => {
const num = self.a;
const denom = pow(u64, 2, self.combo(operand));
self.a = @divTrunc(num, denom);
return .{ self.ip + 2, null };
},
.bxl => {
self.b = self.b ^ operand;
return .{ self.ip + 2, null };
},
.bst => {
const n = self.combo(operand);
self.b = n % 8;
return .{ self.ip + 2, null };
},
.jnz => {
if (self.a == 0) {
return .{ self.ip + 2, null };
} else {
return .{ operand, null };
}
},
.bxc => {
self.b = self.b ^ self.c;
return .{ self.ip + 2, null };
},
.out => {
const n = self.combo(operand);
const out = n % 8;
return .{ self.ip + 2, out };
},
.bdv => {
const num = self.a;
const denom = pow(u64, 2, self.combo(operand));
self.b = @divTrunc(num, denom);
return .{ self.ip + 2, null };
},
.cdv => {
const num = self.a;
const denom = pow(u64, 2, self.combo(operand));
self.c = @divTrunc(num, denom);
return .{ self.ip + 2, null };
},
}
}
fn combo(self: *Self, operand: u3) u64 {
return switch (operand) {
0 => 0,
1 => 1,
2 => 2,
3 => 3,
4 => self.a,
5 => self.b,
6 => self.c,
7 => unreachable,
};
}
};
const Instruction = struct {
opcode: u3,
operand: u3,
};
const Opcode = enum(u3) {
adv = 0,
bxl = 1,
bst = 2,
jnz = 3,
bxc = 4,
out = 5,
bdv = 6,
cdv = 7,
};
fn takeLiteral(input: []const u8, want: []const u8) ?[]const u8 {
if (std.mem.startsWith(u8, input, want)) {
return input[want.len..];
}
return null;
}
fn takeInt(input: []const u8) ?struct { []const u8, u64 } {
var i: usize = 0;
var n: u64 = 0;
while (i < input.len and std.ascii.isDigit(input[i])) : (i += 1) {
n *= 10;
n += input[i] - '0';
}
if (i == 0) {
// Nothing consumed
return null;
}
return .{ input[i..], n };
}
fn quine(allocator: Allocator, program: List(u3)) !u64 {
var stack = List(u64).init(allocator);
defer stack.deinit();
// Easier than figuring out reverse iteration lol
var possible = Set(u64).init(allocator);
defer possible.deinit();
inline for (0..8) |a| {
try stack.append(a);
}
while (stack.popOrNull()) |a| {
var device = Device{
.a = a,
.b = 0,
.c = 0,
.program = program,
.ip = 0,
.allocator = allocator,
};
var out = try device.run();
defer out.deinit();
if (std.mem.endsWith(u3, program.items, out.items)) {
if (program.items.len == out.items.len) {
try possible.put(a);
continue;
}
inline for (0..8) |next| {
try stack.append((a << 3) + next);
}
}
}
var min: u64 = possible.pop().?;
while (possible.pop()) |a| {
if (a < min) {
min = a;
}
}
return min;
}