solve day 17
This commit is contained in:
parent
5c079539df
commit
413e13b601
@ -1,3 +1,5 @@
|
|||||||
# Advent of Code
|
# Advent of Code
|
||||||
|
|
||||||
https://adventofcode.com/2023
|
https://adventofcode.com/2023
|
||||||
|
|
||||||
|
Uses [zig 0.12.0](https://ziglang.org/download/#release-0.12.0)
|
||||||
|
|||||||
226
src/day17.zig
Normal file
226
src/day17.zig
Normal file
@ -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);
|
||||||
|
}
|
||||||
@ -9,6 +9,13 @@ pub fn Point(comptime T: type) type {
|
|||||||
return self.x == other.x and self.y == other.y;
|
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 {
|
pub fn zero() Self {
|
||||||
return Self{ .x = 0, .y = 0 };
|
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) {
|
pub fn to_i32(self: Self) Point(i32) {
|
||||||
return Point(i32){
|
return Point(i32){
|
||||||
.x = @intCast(self.x),
|
.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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user