From 413e13b601b7341eb89bf098df5c5feab3d99213 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Mon, 22 Apr 2024 00:45:39 +0300 Subject: [PATCH] solve day 17 --- README.md | 2 + src/day17.zig | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/point.zig | 17 ++++ 3 files changed, 245 insertions(+) create mode 100644 src/day17.zig diff --git a/README.md b/README.md index 8809e68..2e0e0de 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # Advent of Code https://adventofcode.com/2023 + +Uses [zig 0.12.0](https://ziglang.org/download/#release-0.12.0) diff --git a/src/day17.zig b/src/day17.zig new file mode 100644 index 0000000..864bc30 --- /dev/null +++ b/src/day17.zig @@ -0,0 +1,226 @@ +const std = @import("std"); +const aoc = @import("./aoc.zig"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const PointU8 = @import("./point.zig").Point(u8); +const PointI8 = @import("./point.zig").Point(i8); + +const Direction = enum { + Up, + Down, + Left, + Right, + + pub fn to_offset(self: Direction) PointI8 { + return switch (self) { + .Up => PointI8{ .x = 0, .y = -1 }, + .Down => PointI8{ .x = 0, .y = 1 }, + .Left => PointI8{ .x = -1, .y = 0 }, + .Right => PointI8{ .x = 1, .y = 0 }, + }; + } + + fn inv(self: Direction) Direction { + return switch (self) { + .Up => .Down, + .Down => .Up, + .Left => .Right, + .Right => .Left, + }; + } +}; + +const directions: []const Direction = &[_]Direction{ .Up, .Down, .Left, .Right }; + +const HeatMap = struct { + tiles: std.ArrayList(u8), + width: u32, + height: u32, + + pub fn init(allocator: Allocator, width: u32, height: u32) !HeatMap { + var tiles = try std.ArrayList(u8).initCapacity(allocator, width * height); + errdefer tiles.deinit(); + try tiles.appendNTimes(0, width * height); + + return HeatMap{ + .tiles = tiles, + .width = width, + .height = height, + }; + } + + pub fn deinit(self: HeatMap) void { + self.tiles.deinit(); + } + + pub fn get_idx(self: HeatMap, x: u32, y: u32) usize { + return self.width * y + x; + } + + pub fn in_bounds(self: HeatMap, x: i32, y: i32) bool { + return (0 <= x and x < self.width) and (0 <= y and y < self.height); + } + + pub fn set(self: HeatMap, x: u32, y: u32, value: u8) void { + self.tiles.items[self.get_idx(x, y)] = value; + } + + pub fn get(self: HeatMap, x: u32, y: u32) u8 { + return self.tiles.items[self.get_idx(x, y)]; + } + + pub fn print(self: HeatMap) void { + for (0..self.height) |y| { + for (0..self.width) |x| { + const tile_idx = self.get_idx(@intCast(x), @intCast(y)); + const symbol = self.tiles.items[tile_idx] + '0'; + std.debug.print("{c}", .{symbol}); + } + std.debug.print("\n", .{}); + } + } +}; + +fn parse_input(allocator: Allocator, lines: []const []const u8) !HeatMap { + const height = lines.len; + const width = lines[0].len; + var heat_map = try HeatMap.init(allocator, @intCast(width), @intCast(height)); + + for (0.., lines) |y, line| { + for (0.., line) |x, symbol| { + assert('0' <= symbol and symbol <= '9'); + heat_map.set(@intCast(x), @intCast(y), symbol - '0'); + } + } + + return heat_map; +} + +const QueueItem = struct { + pos: PointU8, + dir: Direction, + consecutive_count: u8, + heat_loss: u32, +}; + +const HeatPriorityQueue = std.PriorityQueue(QueueItem, HeatMap, compareQueueItem); + +fn compareQueueItem(heat_map: HeatMap, a: QueueItem, b: QueueItem) std.math.Order { + const score_a = a.heat_loss + @abs(heat_map.width - a.pos.x) + @abs(heat_map.height - a.pos.y); + const score_b = b.heat_loss + @abs(heat_map.width - b.pos.x) + @abs(heat_map.height - b.pos.y); + return std.math.order(score_a, score_b); +} + +inline fn push_to_queue(queue: *HeatPriorityQueue, heat_map: HeatMap, state: QueueItem, dir: Direction) !void { + const dir_offset = dir.to_offset(); + const neighbour_x = @as(i32, @intCast(state.pos.x)) + dir_offset.x; + const neighbour_y = @as(i32, @intCast(state.pos.y)) + dir_offset.y; + if (!heat_map.in_bounds(neighbour_x, neighbour_y)) return; // Skip out of bounds + + var consecutive_count = state.consecutive_count; + if (state.dir == dir) { + consecutive_count += 1; + } else { + consecutive_count = 1; + } + + const tile_heat_loss = heat_map.get(@intCast(neighbour_x), @intCast(neighbour_y)); + + const next_state = QueueItem{ + .consecutive_count = consecutive_count, + .heat_loss = state.heat_loss + tile_heat_loss, + .dir = dir, + .pos = PointU8{ .x = @intCast(neighbour_x), .y = @intCast(neighbour_y) } + }; + + try queue.add(next_state); +} + +fn solve_min_heat_loss(allocator: Allocator, heat_map: HeatMap, min_steps: u32, max_steps: u32) !?u32 { + var queue = HeatPriorityQueue.init(allocator, heat_map); + defer queue.deinit(); + + var seen = std.AutoHashMap(QueueItem, void).init(allocator); + defer seen.deinit(); + + try queue.add(.{ + .pos = .{ .x = 0, .y = 0 }, + .dir = .Right, + .heat_loss = 0, + .consecutive_count = 1 + }); + try queue.add(.{ + .pos = .{ .x = 0, .y = 0 }, + .dir = .Down, + .heat_loss = 0, + .consecutive_count = 1 + }); + + while (queue.removeOrNull()) |item| { + const state: QueueItem = item; + + if (seen.contains(state)) continue; + try seen.put(state, {}); + + if (state.pos.x == heat_map.width-1 and state.pos.y == heat_map.height-1 and state.consecutive_count >= min_steps) { + return state.heat_loss; + } + + if (state.consecutive_count < min_steps) { + try push_to_queue(&queue, heat_map, state, state.dir); + } else { + for (directions) |dir| { + if (state.dir == dir and state.consecutive_count == max_steps) continue; + + // Skip direction, where it would need to turn around + if (state.dir == dir.inv()) continue; + + try push_to_queue(&queue, heat_map, state, dir); + } + } + } + + return null; +} + +pub fn part1(input: *aoc.Input) !aoc.Result { + const allocator = input.allocator; + const heat_map = try parse_input(allocator, input.lines); + defer heat_map.deinit(); + + const answer = try solve_min_heat_loss(allocator, heat_map, 0, 3); + return .{ .uint = answer.? }; +} + +pub fn part2(input: *aoc.Input) !aoc.Result { + const allocator = input.allocator; + const heat_map = try parse_input(allocator, input.lines); + defer heat_map.deinit(); + + const answer = try solve_min_heat_loss(allocator, heat_map, 4, 10); + return .{ .uint = answer.? }; +} + +const example_input = [_][]const u8{ + "2413432311323", + "3215453535623", + "3255245654254", + "3446585845452", + "4546657867536", + "1438598798454", + "4457876987766", + "3637877979653", + "4654967986887", + "4564679986453", + "1224686865563", + "2546548887735", + "4322674655533", +}; + +test "part 1 example" { + try aoc.expectAnswerUInt(part1, 102, &example_input); +} + +test "part 2 example" { + try aoc.expectAnswerUInt(part2, 94, &example_input); +} diff --git a/src/point.zig b/src/point.zig index 294f323..3636c8f 100644 --- a/src/point.zig +++ b/src/point.zig @@ -9,6 +9,13 @@ pub fn Point(comptime T: type) type { return self.x == other.x and self.y == other.y; } + pub fn neg(self: Self) Self { + return Self{ + .x = -self.x, + .y = -self.y, + }; + } + pub fn zero() Self { return Self{ .x = 0, .y = 0 }; } @@ -20,6 +27,13 @@ pub fn Point(comptime T: type) type { }; } + pub fn sub(self: Self, other: Self) Self { + return Self{ + .x = self.x - other.x, + .y = self.y - other.y, + }; + } + pub fn to_i32(self: Self) Point(i32) { return Point(i32){ .x = @intCast(self.x), @@ -35,3 +49,6 @@ pub fn Point(comptime T: type) type { } }; } + +pub const PointI32 = Point(i32); +pub const PointU32 = Point(u32);