const std = @import("std"); const builtin = @import("builtin"); pub const invalid_index = std.math.inf_u32; const Cell = struct { distance: u32, index: u32, }; fn popNext(front: *std.ArrayList(Cell)) Cell { var min_distance: u32 = std.math.inf_u32; var pop: usize = std.math.inf_u32; for (front.items) |cell, array_index| { if (cell.distance < min_distance) { min_distance = cell.distance; pop = array_index; } } return front.swapRemove(pop); } pub const Map = struct { allocator: std.mem.Allocator, risk_levels: []u32, width: u32, height: u32, pub fn init(allocator: std.mem.Allocator, risk_levels: []u32, width: u32, height: u32) !Map { return Map{ .allocator = allocator, .risk_levels = try allocator.dupe(u32, risk_levels), .width = width, .height = height, }; } pub fn deinit(self: *Map) void { self.allocator.free(self.risk_levels); } pub inline fn neighbors(self: Map, index: u32) [4]u32 { const x: u32 = index % self.width; const y: u32 = index / self.width; return [4]u32{ if (x > 0) index - 1 else invalid_index, if (x < self.width - 1) index + 1 else invalid_index, if (y > 0) index - self.width else invalid_index, if (y < self.height - 1) index + self.width else invalid_index, }; } pub fn find_path(self: Map) !u32 { var front = try std.ArrayList(Cell).initCapacity(self.allocator, 10000); defer front.deinit(); var visited = try self.allocator.alloc(bool, self.risk_levels.len); defer self.allocator.free(visited); std.mem.set(bool, visited, false); var distances = try self.allocator.alloc(u32, self.risk_levels.len); defer self.allocator.free(distances); std.mem.set(u32, distances, std.math.inf_u32); try front.append(Cell{ .distance = 0, .index = 0, }); var num_visited: u32 = 0; iterate: while (front.items.len > 0) { var cell = popNext(&front); if (visited[cell.index]) { continue :iterate; } visited[cell.index] = true; num_visited += 1; var nbors = self.neighbors(cell.index); for (nbors) |nbor| { if (nbor == invalid_index) { continue; } var distance: u32 = cell.distance + self.risk_levels[nbor]; if (distance < distances[nbor]) { distances[nbor] = distance; } try front.append(Cell{ .distance = distance, .index = nbor, }); } if (num_visited % 10000 == 0) { std.debug.print("num_visited: {}/{}\n", .{ num_visited, self.risk_levels.len }); } } var target: u32 = (self.width * self.height) - 1; return distances[target]; } pub fn print_risk_levels(self: Map, split_width: u32) void { var y: u32 = 0; while (y < self.height) : (y += 1) { if (y > 0 and y % split_width == 0) { std.debug.print("\n", .{}); } var x: u32 = 0; var base_i: u32 = y * self.width; while (x < self.width / split_width) : (x += 1) { const start = base_i + (x * split_width); const stop = start + split_width; std.debug.print(" {any}", .{self.risk_levels[start..stop]}); } std.debug.print("\n", .{}); } } pub fn expand(self: *Map, times: u32) !void { var old_width = self.width; var old_height = self.height; self.width *= times; self.height *= times; { var risk_levels = try self.allocator.alloc(u32, self.risk_levels.len * (times * times)); var y: u32 = 0; while (y < old_height) : (y += 1) { var x: u32 = 0; while (x < old_width) : (x += 1) { risk_levels[(y * self.width) + x] = self.risk_levels[(y * old_width) + x]; } } self.allocator.free(self.risk_levels); self.risk_levels = risk_levels; } var i_y: u32 = 0; var base_i: u32 = 0; while (i_y < times) : (i_y += 1) { var i_x: u32 = if (i_y == 0) 1 else 0; while (i_x < times) : (i_x += 1) { var current_i = ((i_y * old_height) * self.width) + (i_x * old_width); var y: u32 = 0; while (y < old_height) : (y += 1) { var x: u32 = 0; while (x < old_width) : (x += 1) { var internal_offset = (y * self.width) + x; var current = self.risk_levels[base_i + internal_offset]; self.risk_levels[current_i + internal_offset] = if (current + 1 > 9) 1 else current + 1; } } base_i += old_width; } base_i = i_y * (self.width * old_height); } // std.log.debug("expanded risk levels:", .{}); // self.print_risk_levels(old_width); } }; pub fn readInput(allocator: std.mem.Allocator, reader: anytype) !Map { var risk_levels = std.ArrayList(u32).init(allocator); defer risk_levels.deinit(); var width: u32 = 0; var height: u32 = 0; while (true) { var line: []u8 = undefined; if (builtin.os.tag == .windows and !builtin.is_test) { // NOTE: Read another byte on windows due to two-byte eol. // NOTE: Check if in testing since tests only add single-byte eol in multiline strings. line = reader.readUntilDelimiterAlloc(allocator, '\r', 512) catch break; _ = try reader.readByte(); } else { line = reader.readUntilDelimiterAlloc(allocator, '\n', 512) catch break; } defer allocator.free(line); if (line.len > width) { width = @intCast(u32, line.len); } height += 1; for (line) |char| { const risk: u32 = char - '0'; try risk_levels.append(risk); } } return try Map.init(allocator, risk_levels.items, width, height); } pub fn task1(input: *Map) !u32 { return input.find_path(); } pub fn task2(input: *Map) !u32 { try input.expand(5); return input.find_path(); } pub fn main() !void { var buffer: [8000000]u8 = undefined; var fixed_buffer = std.heap.FixedBufferAllocator.init(&buffer); const allocator = fixed_buffer.allocator(); const file = try std.fs.cwd().openFile("input.txt", .{ .read = true }); defer file.close(); var input = try readInput(allocator, file.reader()); defer input.deinit(); const task_1_result = try task1(&input); std.log.info("Task 1 result: {}", .{task_1_result}); const task_2_result = try task2(&input); std.log.info("Task 2 result: {}", .{task_2_result}); }