"solve" day 21
This commit is contained in:
parent
b59bb9c200
commit
ca8e8cf3e9
254
src/day21.zig
Normal file
254
src/day21.zig
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user