"solve" day 21

This commit is contained in:
Rokas Puzonas 2024-06-01 14:42:26 +03:00
parent b59bb9c200
commit ca8e8cf3e9

254
src/day21.zig Normal file
View File

@ -0,0 +1,254 @@
const aoc = @import("./aoc.zig");
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const PointU32 = @import("./point.zig").PointU32;
const PointI32 = @import("./point.zig").PointI32;
const Tile = enum {
GardenPlot,
Rock,
Start,
fn fromU8(char: u8) ?Tile {
return switch (char) {
'.' => .GardenPlot,
'#' => .Rock,
'S' => .Start,
else => null
};
}
};
const Map = struct {
allocator: Allocator,
tiles: []Tile,
width: usize,
height: usize,
fn deinit(self: Map) void {
self.allocator.free(self.tiles);
}
};
fn parseInput(allocator: Allocator, lines: []const []const u8) !Map {
const width = lines[0].len;
const height = lines.len;
const parsed = Map {
.allocator = allocator,
.width = width,
.height = height,
.tiles = try allocator.alloc(Tile, width * height)
};
errdefer parsed.deinit();
for (0.., lines) |y, line| {
for (0.., line) |x, char| {
const index = y * width + x;
parsed.tiles[index] = Tile.fromU8(char) orelse return error.InvalidTile;
}
}
return parsed;
}
fn findStart(map: Map) ?PointU32 {
for (0..map.height) |y| {
for (0..map.width) |x| {
if (map.tiles[y * map.width + x] == .Start) {
return PointU32{
.x = @intCast(x),
.y = @intCast(y),
};
}
}
}
return null;
}
fn simulateSteps(allocator: Allocator, map: Map, from: PointU32, num_steps: usize) !u64 {
var answer: usize = 0;
var seen = std.AutoHashMap(PointU32, void).init(allocator);
defer seen.deinit();
var queue = std.ArrayList(struct { point: PointU32, steps_left: usize }).init(allocator);
defer queue.deinit();
try seen.put(from, {});
try queue.append(.{ .point = from, .steps_left = num_steps });
while (queue.items.len > 0) {
const queue_item = queue.orderedRemove(0);
if (queue_item.steps_left % 2 == 0) {
answer += 1;
}
if (queue_item.steps_left == 0) {
continue;
}
const point = queue_item.point;
const offsets = .{
.{ 1, 0 },
.{ 0, 1 },
.{ -1, 0 },
.{ 0, -1 },
};
inline for (offsets) |offset| {
const nx: i32 = @as(i32, @intCast(point.x)) + offset[0];
const ny: i32 = @as(i32, @intCast(point.y)) + offset[1];
if ((0 <= nx and nx < map.width) and (0 <= ny and ny < map.height)) {
const next_point = PointU32{
.x = @intCast(nx),
.y = @intCast(ny),
};
if (map.tiles[next_point.x * map.width + next_point.y] != .Rock and !seen.contains(next_point)) {
try queue.append(.{ .point = next_point, .steps_left = queue_item.steps_left - 1 });
try seen.put(next_point, {});
}
}
}
}
return answer;
}
pub fn part1(input: *aoc.Input) !aoc.Result {
const allocator = input.allocator;
const map = try parseInput(allocator, input.lines);
defer map.deinit();
const start_location = findStart(map) orelse return error.NoStart;
const answer = try simulateSteps(allocator, map, start_location, 64);
return .{ .uint = answer };
}
fn isColumnEmpty(map: Map, x: usize) bool {
for (0..map.height) |y| {
const idx = y * map.width + x;
if (map.tiles[idx] == .Rock) {
return false;
}
}
return true;
}
fn isRowEmpty(map: Map, y: usize) bool {
for (0..map.width) |x| {
const idx = y * map.width + x;
if (map.tiles[idx] == .Rock) {
return false;
}
}
return true;
}
// Props to this https://github.com/villuna/aoc23/wiki/A-Geometric-solution-to-advent-of-code-2023,-day-21
// For writting up a nice solution explanation
// Also this was very useful: https://www.youtube.com/watch?v=9UOMZSL0JTg
pub fn part2(input: *aoc.Input) !aoc.Result {
const allocator = input.allocator;
const map = try parseInput(allocator, input.lines);
defer map.deinit();
const start_location = findStart(map) orelse return error.NoStart;
// "Some" assumptions about the data must be made
assert(map.width == map.height); // The grid is a square
assert(start_location.x == map.width / 2 and start_location.y == map.height / 2); // Starting location is in the center
assert(map.width % 2 == 1); // The size of the square grid is odd
assert(isRowEmpty(map, start_location.y) and isColumnEmpty(map, start_location.x));
assert(isRowEmpty(map, 0) and isRowEmpty(map, map.height-1));
assert(isColumnEmpty(map, 0) and isColumnEmpty(map, map.width-1));
const map_size: u32 = @intCast(map.width);
const num_steps = 26501365;
const n = (num_steps - map_size / 2) / map_size;
var answer: usize = 0;
const odd_points = try simulateSteps(allocator, map, start_location, 2*map_size + 1);
const odd_squares = std.math.pow(usize, (n - 1) / 2 * 2 + 1, 2);
answer += odd_points * odd_squares;
const even_points = try simulateSteps(allocator, map, start_location, 2*map_size);
const even_squares = std.math.pow(usize, n / 2 * 2, 2);
answer += even_points * even_squares;
const cardinal_corners = .{
PointU32{ .x = start_location.x, .y = map_size - 1 },
PointU32{ .x = start_location.x, .y = 0 },
PointU32{ .x = map_size - 1, .y = start_location.y },
PointU32{ .x = 0, .y = start_location.y }
};
inline for(cardinal_corners) |corner| {
answer += try simulateSteps(allocator, map, corner, map_size - 1);
}
const small_corners = .{
PointU32{ .x = 0 , .y = map_size - 1 } ,
PointU32{ .x = map_size - 1 , .y = map_size - 1 } ,
PointU32{ .x = 0 , .y = 0 } ,
PointU32{ .x = map_size - 1 , .y = 0 } ,
};
inline for(small_corners) |corner| {
const points = try simulateSteps(allocator, map, corner, map_size / 2 - 1);
answer += points * n;
}
const large_corners = .{
PointU32{ .x = 0 , .y = map_size - 1 } ,
PointU32{ .x = map_size - 1 , .y = map_size - 1 } ,
PointU32{ .x = 0 , .y = 0 } ,
PointU32{ .x = map_size - 1 , .y = 0 } ,
};
inline for(large_corners) |corner| {
const points = try simulateSteps(allocator, map, corner, map_size * 3 / 2 - 1);
answer += points * (n - 1);
}
return .{ .uint = answer };
}
test "part 1 example" {
const example_input = [_][]const u8{
"...........",
".....###.#.",
".###.##..#.",
"..#.#...#..",
"....#.#....",
".##..S####.",
".##..#...#.",
".......##..",
".##.#.####.",
".##..##.##.",
"...........",
};
try aoc.expectAnswerUInt(part1, 42, &example_input);
}
test "part 2 example" {
const modified_example_input = [_][]const u8{
"...........",
"......##.#.",
".###..#..#.",
"..#.#...#..",
"....#.#....",
".....S.....",
".##......#.",
".......##..",
".##.#.####.",
".##...#.##.",
"...........",
};
try aoc.expectAnswerUInt(part2, 528192639411648, &modified_example_input);
}