aoc/day8/src/main.zig

223 lines
6.4 KiB
Zig

const std = @import("std");
pub fn main() anyerror!void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
var gpa = &arena.allocator;
var program = try Program.init(gpa);
defer program.*.deinit();
var f = try std.fs.cwd().openFile("input", .{});
var contents = try f.readToEndAlloc(gpa, std.math.maxInt(u32));
var it = std.mem.tokenize(contents, "\n");
while (it.next()) |line| {
var s = try Statement.from_string(line);
try program.code.append(s);
}
std.log.debug("Loaded program with {} statements", .{program.code.items.len});
_ = try program.run();
// Part 2
// Exactly one nop should be a jmp or a jmp a noop
var i : usize = 0;
while (i < program.code.items.len) : (i += 1) {
if (program.code.items[i].op == .acc) {
continue;
}
// Mutate
program.code.items[i].op = switch(program.code.items[i].op) {
.acc => Operation.acc,
.jmp => Operation.nop,
.nop => Operation.jmp,
};
// Test
var run_ok = program.run() catch false;
// Undo mutation
program.code.items[i].op = switch(program.code.items[i].op) {
.acc => Operation.acc,
.jmp => Operation.nop,
.nop => Operation.jmp,
};
if (run_ok) {
std.log.info("By changing statement {}, the program exited okay", .{i});
break;
}
}
}
pub const Operation = enum {
nop,
acc,
jmp
};
pub const ParseError = error {
UnknownStatement,
};
pub const Statement = struct {
op: Operation,
arg: i32,
/// Modifies acc and returns the offset for the next operation
pub fn execute(self: *Statement, acc: *i32) i32 {
var delta : i32 = switch (self.op) {
.nop => 1,
.jmp => self.arg,
.acc => 1,
};
if (self.op == .acc) {
acc.* += self.arg;
}
return delta;
}
pub fn from_string(line: []const u8) !Statement {
var arg : i32 = 0;
var op : Operation = undefined;
var it = std.mem.tokenize(line, " ");
if (it.next()) |l| {
if (std.mem.eql(u8, l, "nop")) {
op = Operation.nop;
}
else if (std.mem.eql(u8, l, "acc")) {
op = Operation.acc;
}
else if (std.mem.eql(u8, l, "jmp")) {
op = Operation.jmp;
}
else {
std.log.err("Unknown statement: '{}'", .{l});
return ParseError.UnknownStatement;
}
}
else {
// Error, and operator is required
unreachable;
}
if (it.next()) |l| {
arg = atoi(l);
}
else {
// Error, we need an arg
unreachable;
}
while(it.next()) |l| {
// Error, there shouldn't be anything else on the line
unreachable;
}
return Statement {
.op = op,
.arg = arg,
};
}
};
pub const Program = struct {
code: std.ArrayList(Statement),
allocator: *std.mem.Allocator,
accumulator: i32 = 0,
pub fn init(a: *std.mem.Allocator) !*Program {
const self = try a.create(Program);
errdefer a.destroy(self);
self.* = Program {
.code = std.ArrayList(Statement).init(a),
.allocator = a,
};
return self;
}
pub fn deinit(self: *Program) void {
self.code.deinit();
self.allocator.destroy(self);
}
pub fn run(self: *Program) !bool {
self.accumulator = 0;
var next_statement_index : usize = 0;
// Statement counter
var map = std.hash_map.AutoHashMap(usize, u32).init(self.allocator);
defer map.deinit();
var ended_ok = false;
while (true) {
// Check if we have already exectured this index
var n_accumulator : i32 = self.accumulator;
if (map.contains(next_statement_index)) {
std.log.warn("Infinite loop detected. Statement {} would have run twice",
.{next_statement_index});
std.log.warn("Acc value at break: {}", .{self.accumulator});
break;
}
else {
try map.put(next_statement_index, 1);
}
var delta = self.code.items[next_statement_index].execute(&n_accumulator);
//std.log.debug("Before {}, After {} [statement {} new offset {}] {}",
// .{self.accumulator, n_accumulator, next_statement_index,
// delta, self.code.items[next_statement_index]});
if (delta < 0) {
next_statement_index -= @intCast(usize, try std.math.absInt(delta));
}
else {
next_statement_index += @intCast(usize, delta);
}
std.debug.assert(next_statement_index >= 0);
if (next_statement_index >= self.code.items.len) {
std.log.warn("Reached end of code, or jumping past end ({}, {} statements).",
.{next_statement_index, self.code.items.len});
std.log.warn("Acc value at end: {}", .{self.accumulator});
ended_ok = true; // maybe...
break;
}
self.accumulator = n_accumulator;
}
return ended_ok;
}
};
fn atoi(a: []const u8) i32 {
var i : i32 = 0;
var mul : i32 = 1;
var start : usize = 0;
if (a[0] == '-' or a[0] == '+') {
start = 1;
if (a[0] == '-') {
mul *= -1;
}
}
for(a[start..]) |v, k| {
if (! std.ascii.isDigit(v)) {
std.log.warn("Byte {x} is not a digit", .{v});
continue;
}
// 48 is '0' in ascii
std.debug.assert(v >= 48 and v < 58);
i += @as(i32, @as(i32, (v - 48)) * std.math.pow(i32, 10, @intCast(i32, a.len - k - 1 - start)));
}
//std.log.debug("{x} --> {}", .{a, i});
return i * mul;
}
test "atoi_regular" {
var i = atoi("1234");
std.testing.expectEqual(i, 1234);
}
test "atoi_pos" {
var i = atoi("+1234");
std.testing.expectEqual(i, 1234);
}
test "atoi_neg" {
var i = atoi("-1234");
std.testing.expectEqual(i, -1234);
}