Compare commits
2 Commits
ca8e8cf3e9
...
e97c8e44bf
Author | SHA1 | Date | |
---|---|---|---|
e97c8e44bf | |||
13a6c53fa6 |
365
src/day22.zig
Normal file
365
src/day22.zig
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
const aoc = @import("./aoc.zig");
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const Vec3 = struct {
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
z: i32
|
||||||
|
};
|
||||||
|
|
||||||
|
const Brick = struct {
|
||||||
|
v1: Vec3,
|
||||||
|
v2: Vec3,
|
||||||
|
|
||||||
|
fn cubeIterator(self: Brick) CubeIterator {
|
||||||
|
const dx = self.v2.x - self.v1.x;
|
||||||
|
const dy = self.v2.y - self.v1.y;
|
||||||
|
const dz = self.v2.z - self.v1.z;
|
||||||
|
|
||||||
|
return CubeIterator{
|
||||||
|
.current = self.v1,
|
||||||
|
.count = @abs(dx) + @abs(dy) + @abs(dz) + 1,
|
||||||
|
.direction = Vec3{
|
||||||
|
.x = @min(@max(dx, -1), 1),
|
||||||
|
.y = @min(@max(dy, -1), 1),
|
||||||
|
.z = @min(@max(dz, -1), 1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const CubeIterator = struct {
|
||||||
|
current: Vec3,
|
||||||
|
direction: Vec3,
|
||||||
|
count: u32,
|
||||||
|
|
||||||
|
fn next(self: *CubeIterator) ?Vec3 {
|
||||||
|
if (self.count == 0) return null;
|
||||||
|
const result = self.current;
|
||||||
|
|
||||||
|
self.count -= 1;
|
||||||
|
self.current.x += self.direction.x;
|
||||||
|
self.current.y += self.direction.y;
|
||||||
|
self.current.z += self.direction.z;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Bricks = std.ArrayList(Brick);
|
||||||
|
|
||||||
|
const BrickId = u11;
|
||||||
|
const BrickVolume = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
bricks: []Brick,
|
||||||
|
|
||||||
|
cube_lookup: []?BrickId,
|
||||||
|
x_size: u32,
|
||||||
|
y_size: u32,
|
||||||
|
z_size: u32,
|
||||||
|
|
||||||
|
fn init(allocator: Allocator, bricks: []const Brick) !BrickVolume {
|
||||||
|
const local_bricks = try allocator.dupe(Brick, bricks);
|
||||||
|
errdefer allocator.free(local_bricks);
|
||||||
|
|
||||||
|
const upper_bounds = getUpperBounds(bricks);
|
||||||
|
const x_size: u32 = @intCast(upper_bounds.x + 1);
|
||||||
|
const y_size: u32 = @intCast(upper_bounds.y + 1);
|
||||||
|
const z_size: u32 = @intCast(upper_bounds.z + 1);
|
||||||
|
const cube_lookup = try allocator.alloc(?BrickId, x_size * y_size * z_size);
|
||||||
|
errdefer allocator.free(cube_lookup);
|
||||||
|
@memset(cube_lookup, null);
|
||||||
|
|
||||||
|
const self = BrickVolume{
|
||||||
|
.allocator = allocator,
|
||||||
|
.bricks = local_bricks,
|
||||||
|
.cube_lookup = cube_lookup,
|
||||||
|
.x_size = x_size,
|
||||||
|
.y_size = y_size,
|
||||||
|
.z_size = z_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (0.., bricks) |brick_id, brick| {
|
||||||
|
self.addToLookup(@intCast(brick_id), brick);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getIndex(self: BrickVolume, x: u32, y: u32, z: u32) u32 {
|
||||||
|
assert(x < self.x_size);
|
||||||
|
assert(y < self.y_size);
|
||||||
|
assert(z < self.z_size);
|
||||||
|
return z * self.x_size * self.y_size + y * self.x_size + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getIndexVec3(self: BrickVolume, vec3: Vec3) u32 {
|
||||||
|
return self.getIndex(
|
||||||
|
@intCast(vec3.x),
|
||||||
|
@intCast(vec3.y),
|
||||||
|
@intCast(vec3.z),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: BrickVolume) void {
|
||||||
|
self.allocator.free(self.bricks);
|
||||||
|
self.allocator.free(self.cube_lookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addToLookup(self: BrickVolume, brick_id: BrickId, brick: Brick) void {
|
||||||
|
var cube_iter = brick.cubeIterator();
|
||||||
|
while (cube_iter.next()) |cube| {
|
||||||
|
const cube_idx = self.getIndexVec3(cube);
|
||||||
|
assert(self.cube_lookup[cube_idx] == null);
|
||||||
|
self.cube_lookup[cube_idx] = brick_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn removeFromLookup(self: BrickVolume, brick: Brick) void {
|
||||||
|
var cube_iter = brick.cubeIterator();
|
||||||
|
while (cube_iter.next()) |cube| {
|
||||||
|
const cube_idx = self.getIndexVec3(cube);
|
||||||
|
assert(self.cube_lookup[cube_idx] != null);
|
||||||
|
self.cube_lookup[cube_idx] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn moveBrickDown(self: BrickVolume, brick_id: BrickId, dz: u32) void {
|
||||||
|
var brick = &self.bricks[brick_id];
|
||||||
|
|
||||||
|
self.removeFromLookup(brick.*);
|
||||||
|
|
||||||
|
brick.v1.z -= @intCast(dz);
|
||||||
|
brick.v2.z -= @intCast(dz);
|
||||||
|
assert(brick.v1.z > 0);
|
||||||
|
assert(brick.v2.z > 0);
|
||||||
|
|
||||||
|
self.addToLookup(brick_id, brick.*);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canBrickMoveDown(self: BrickVolume, brick_id: BrickId, dz: u32) bool {
|
||||||
|
var brick = self.bricks[brick_id];
|
||||||
|
|
||||||
|
var cube_iter = brick.cubeIterator();
|
||||||
|
cube_iter.current.z -= @intCast(dz);
|
||||||
|
while (cube_iter.next()) |cube| {
|
||||||
|
if (cube.z <= 0) return false;
|
||||||
|
|
||||||
|
const cube_idx = self.getIndexVec3(cube);
|
||||||
|
if (self.cube_lookup[cube_idx] != null and self.cube_lookup[cube_idx] != brick_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn parseVec3(str: []const u8) !Vec3 {
|
||||||
|
var components: [3]i32 = undefined;
|
||||||
|
var count: u32 = 0;
|
||||||
|
|
||||||
|
var iter = std.mem.splitScalar(u8, str, ',');
|
||||||
|
while (iter.next()) |part| {
|
||||||
|
if (count == 3) return error.TooManyComponents;
|
||||||
|
components[count] = try std.fmt.parseInt(i32, part, 10);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 3) {
|
||||||
|
return error.MissingComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Vec3{
|
||||||
|
.x = components[0],
|
||||||
|
.y = components[1],
|
||||||
|
.z = components[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseBrick(line: []const u8) !Brick {
|
||||||
|
const separator = std.mem.indexOfScalar(u8, line, '~') orelse return error.MissingSeparator;
|
||||||
|
return Brick{
|
||||||
|
.v1 = try parseVec3(line[0..separator]),
|
||||||
|
.v2 = try parseVec3(line[(separator+1)..]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseInput(allocator: Allocator, lines: []const []const u8) !Bricks {
|
||||||
|
var bricks = Bricks.init(allocator);
|
||||||
|
errdefer bricks.deinit();
|
||||||
|
|
||||||
|
for (lines) |line| {
|
||||||
|
try bricks.append(try parseBrick(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
return bricks;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getUpperBounds(bricks: []const Brick) Vec3 {
|
||||||
|
var upper = bricks[0].v1;
|
||||||
|
|
||||||
|
for (bricks) |brick| {
|
||||||
|
inline for (.{ brick.v1, brick.v2 }) |p| {
|
||||||
|
upper.x = @max(upper.x, p.x);
|
||||||
|
upper.y = @max(upper.y, p.y);
|
||||||
|
upper.z = @max(upper.z, p.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return upper;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dropBricksUntilStable(brick_volume: BrickVolume) void {
|
||||||
|
var count: u32 = 0;
|
||||||
|
var brick_moved = true;
|
||||||
|
while (brick_moved) {
|
||||||
|
brick_moved = false;
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
for (0..brick_volume.bricks.len) |i| {
|
||||||
|
const brick_id: BrickId = @intCast(i);
|
||||||
|
if (brick_volume.canBrickMoveDown(brick_id, 1)) {
|
||||||
|
brick_volume.moveBrickDown(brick_id, 1);
|
||||||
|
brick_moved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part1(input: *aoc.Input) !aoc.Result {
|
||||||
|
const allocator = input.allocator;
|
||||||
|
const bricks = try parseInput(allocator, input.lines);
|
||||||
|
defer bricks.deinit();
|
||||||
|
|
||||||
|
var brick_volume = try BrickVolume.init(allocator, bricks.items);
|
||||||
|
defer brick_volume.deinit();
|
||||||
|
|
||||||
|
dropBricksUntilStable(brick_volume);
|
||||||
|
|
||||||
|
var answer: u32 = 0;
|
||||||
|
for (0.., brick_volume.bricks) |i, brick| {
|
||||||
|
const brick_id: BrickId = @intCast(i);
|
||||||
|
|
||||||
|
var can_be_removed = true;
|
||||||
|
brick_volume.removeFromLookup(brick);
|
||||||
|
for (0..brick_volume.bricks.len) |j| {
|
||||||
|
if (i == j) continue;
|
||||||
|
|
||||||
|
if (brick_volume.canBrickMoveDown(@intCast(j), 1)) {
|
||||||
|
can_be_removed = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
brick_volume.addToLookup(brick_id, brick);
|
||||||
|
|
||||||
|
if (can_be_removed) {
|
||||||
|
answer += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .uint = answer };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(input: *aoc.Input) !aoc.Result {
|
||||||
|
const allocator = input.allocator;
|
||||||
|
const bricks = try parseInput(allocator, input.lines);
|
||||||
|
defer bricks.deinit();
|
||||||
|
|
||||||
|
var brick_volume = try BrickVolume.init(allocator, bricks.items);
|
||||||
|
defer brick_volume.deinit();
|
||||||
|
|
||||||
|
dropBricksUntilStable(brick_volume);
|
||||||
|
|
||||||
|
var bricks_deps = std.ArrayList(std.ArrayList(BrickId)).init(allocator);
|
||||||
|
for (0..brick_volume.bricks.len) |_| {
|
||||||
|
try bricks_deps.append(std.ArrayList(BrickId).init(allocator));
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
for (bricks_deps.items) |brick_deps| {
|
||||||
|
brick_deps.deinit();
|
||||||
|
}
|
||||||
|
bricks_deps.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0.., brick_volume.bricks) |i, brick| {
|
||||||
|
const brick_id: BrickId = @intCast(i);
|
||||||
|
var brick_deps: *std.ArrayList(BrickId) = &bricks_deps.items[brick_id];
|
||||||
|
|
||||||
|
var cube_iter = brick.cubeIterator();
|
||||||
|
cube_iter.current.z -= 1;
|
||||||
|
while (cube_iter.next()) |cube| {
|
||||||
|
if (cube.z >= brick_volume.z_size) continue;
|
||||||
|
const cube_idx = brick_volume.getIndexVec3(cube);
|
||||||
|
const brick_at_cube = brick_volume.cube_lookup[cube_idx];
|
||||||
|
|
||||||
|
if (brick_at_cube != null and brick_at_cube != brick_id) {
|
||||||
|
if (std.mem.indexOfScalar(BrickId, brick_deps.items, brick_at_cube.?) == null) {
|
||||||
|
try brick_deps.append(brick_at_cube.?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var answer: u64 = 0;
|
||||||
|
var removed_bricks = try allocator.alloc(bool, bricks.items.len);
|
||||||
|
defer allocator.free(removed_bricks);
|
||||||
|
|
||||||
|
for (0..bricks.items.len) |i| {
|
||||||
|
@memset(removed_bricks, false);
|
||||||
|
removed_bricks[i] = true;
|
||||||
|
|
||||||
|
var unstable = true;
|
||||||
|
while (unstable) {
|
||||||
|
unstable = false;
|
||||||
|
|
||||||
|
for (0.., bricks_deps.items) |j, brick_deps| {
|
||||||
|
const brick = brick_volume.bricks[j];
|
||||||
|
|
||||||
|
// Any brick on the ground will always be stable
|
||||||
|
if (brick.v1.z == 1 or brick.v2.z == 1) continue;
|
||||||
|
|
||||||
|
// Do not check removed bricks
|
||||||
|
if (removed_bricks[j]) continue;
|
||||||
|
|
||||||
|
var is_brick_supported = false;
|
||||||
|
for (brick_deps.items) |brick_dep| {
|
||||||
|
if (!removed_bricks[brick_dep]) {
|
||||||
|
is_brick_supported = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_brick_supported) {
|
||||||
|
unstable = true;
|
||||||
|
removed_bricks[j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removed_count = std.mem.count(bool, removed_bricks, &[_]bool{ true });
|
||||||
|
answer += (removed_count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .uint = answer };
|
||||||
|
}
|
||||||
|
|
||||||
|
const example_input = [_][]const u8{
|
||||||
|
"1,0,1~1,2,1",
|
||||||
|
"0,0,2~2,0,2",
|
||||||
|
"0,2,3~2,2,3",
|
||||||
|
"0,0,4~0,2,4",
|
||||||
|
"2,0,5~2,2,5",
|
||||||
|
"0,1,6~2,1,6",
|
||||||
|
"1,1,8~1,1,9",
|
||||||
|
};
|
||||||
|
|
||||||
|
test "part 1 example" {
|
||||||
|
try aoc.expectAnswerUInt(part1, 5, &example_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "part 2 example" {
|
||||||
|
try aoc.expectAnswerUInt(part2, 7, &example_input);
|
||||||
|
}
|
432
src/day23.zig
Normal file
432
src/day23.zig
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
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 neighbours = .{
|
||||||
|
.{ 1, 0 },
|
||||||
|
.{ -1, 0 },
|
||||||
|
.{ 0, 1 },
|
||||||
|
.{ 0, -1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const Tile = enum {
|
||||||
|
Empty,
|
||||||
|
Wall,
|
||||||
|
DownSlope,
|
||||||
|
UpSlope,
|
||||||
|
RightSlope,
|
||||||
|
LeftSlope,
|
||||||
|
|
||||||
|
fn fromU8(char: u8) ?Tile {
|
||||||
|
return switch(char) {
|
||||||
|
'.' => .Empty,
|
||||||
|
'#' => .Wall,
|
||||||
|
'v' => .DownSlope,
|
||||||
|
'>' => .RightSlope,
|
||||||
|
'<' => .LeftSlope,
|
||||||
|
'^' => .UpSlope,
|
||||||
|
else => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getSlopeDirection(self: Tile) ?PointI32 {
|
||||||
|
return switch (self) {
|
||||||
|
.DownSlope => PointI32{ .x = 0, .y = 1 },
|
||||||
|
.UpSlope => PointI32{ .x = 0, .y = -1 },
|
||||||
|
.LeftSlope => PointI32{ .x = -1, .y = 0 },
|
||||||
|
.RightSlope => PointI32{ .x = 1, .y = 0 },
|
||||||
|
else => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Map = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
tiles: []Tile,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
|
||||||
|
fn init(allocator: Allocator, width: u32, height: u32) !Map {
|
||||||
|
return Map{
|
||||||
|
.tiles = try allocator.alloc(Tile, width * height),
|
||||||
|
.allocator = allocator,
|
||||||
|
.width = width,
|
||||||
|
.height = height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(self: Map, x: u32, y: u32) Tile {
|
||||||
|
return self.tiles[self.getIndex(x, y)];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getIndex(self: Map, x: u32, y: u32) usize {
|
||||||
|
assert(0 <= x and x < self.width);
|
||||||
|
assert(0 <= y and y < self.height);
|
||||||
|
return y * self.width + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 map = try Map.init(allocator, @intCast(width), @intCast(height));
|
||||||
|
errdefer map.deinit();
|
||||||
|
|
||||||
|
for (0.., lines) |y, line| {
|
||||||
|
for (0.., line) |x, char| {
|
||||||
|
const idx = y * width + x;
|
||||||
|
map.tiles[idx] = Tile.fromU8(char) orelse return error.InvalidTile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findEmptySpot(map: Map, y: u32) ?u32 {
|
||||||
|
for (0..map.width) |x| {
|
||||||
|
const idx = y * map.width + x;
|
||||||
|
if (map.tiles[idx] == .Empty) {
|
||||||
|
return @intCast(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodeRegionId = u8;
|
||||||
|
const RegionConnectionsArray = std.BoundedArray(NodeRegionId, 4);
|
||||||
|
const NodeRegion = struct {
|
||||||
|
size: u32,
|
||||||
|
connections: RegionConnectionsArray
|
||||||
|
};
|
||||||
|
|
||||||
|
const NodeMap = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
regions: []NodeRegion,
|
||||||
|
region_map: []?NodeRegionId,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
|
||||||
|
fn init(allocator: Allocator, map: Map) !NodeMap {
|
||||||
|
const region_map = try allocator.alloc(?NodeRegionId, map.width * map.height);
|
||||||
|
errdefer allocator.free(region_map);
|
||||||
|
@memset(region_map, null);
|
||||||
|
|
||||||
|
var region_count: NodeRegionId = 0;
|
||||||
|
while (findUnmarkedPoint(map, region_map)) |point| {
|
||||||
|
try floodFillRegion(allocator, point, region_count, map, region_map);
|
||||||
|
|
||||||
|
region_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var regions = try allocator.alloc(NodeRegion, region_count);
|
||||||
|
errdefer allocator.free(regions);
|
||||||
|
|
||||||
|
for (regions) |*region| {
|
||||||
|
region.size = 0;
|
||||||
|
region.connections = RegionConnectionsArray.init(0) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0..map.height) |y| {
|
||||||
|
for (0..map.width) |x| {
|
||||||
|
const point = PointU32{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = @intCast(y)
|
||||||
|
};
|
||||||
|
|
||||||
|
const slope_dir = map.get(point.x, point.y).getSlopeDirection();
|
||||||
|
if (slope_dir == null) continue;
|
||||||
|
|
||||||
|
const from_point = point.toI32().sub(slope_dir.?).toU32();
|
||||||
|
const to_point = point.toI32().add(slope_dir.?).toU32();
|
||||||
|
|
||||||
|
const from_region = region_map[map.getIndex(from_point.x, from_point.y)].?;
|
||||||
|
const to_region = region_map[map.getIndex(to_point.x, to_point.y)].?;
|
||||||
|
|
||||||
|
try regions[from_region].connections.append(to_region);
|
||||||
|
|
||||||
|
if (isIntersection(map, to_point.x, to_point.y)) {
|
||||||
|
region_map[map.getIndex(point.x, point.y)] = from_region;
|
||||||
|
} else {
|
||||||
|
region_map[map.getIndex(point.x, point.y)] = to_region;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (region_map) |maybe_region_id| {
|
||||||
|
if (maybe_region_id) |region_id| {
|
||||||
|
regions[region_id].size += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NodeMap{
|
||||||
|
.allocator = allocator,
|
||||||
|
.regions = regions,
|
||||||
|
.region_map = region_map,
|
||||||
|
.width = map.width,
|
||||||
|
.height = map.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: NodeMap) void {
|
||||||
|
self.allocator.free(self.regions);
|
||||||
|
self.allocator.free(self.region_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findUnmarkedPoint(map: Map, region_map: []const ?NodeRegionId) ?PointU32 {
|
||||||
|
for (0..map.height) |y| {
|
||||||
|
for (0..map.width) |x| {
|
||||||
|
const idx = y * map.width + x;
|
||||||
|
if (region_map[idx] == null and map.tiles[idx] == .Empty) {
|
||||||
|
return PointU32{
|
||||||
|
.x = @intCast(x),
|
||||||
|
.y = @intCast(y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn floodFillRegion(
|
||||||
|
allocator: Allocator,
|
||||||
|
from: PointU32,
|
||||||
|
region_id: NodeRegionId,
|
||||||
|
map: Map,
|
||||||
|
region_map: []?NodeRegionId
|
||||||
|
) !void {
|
||||||
|
var stack = std.ArrayList(PointU32).init(allocator);
|
||||||
|
defer stack.deinit();
|
||||||
|
|
||||||
|
try stack.append(from);
|
||||||
|
region_map[map.getIndex(from.x, from.y)] = region_id;
|
||||||
|
|
||||||
|
while (stack.popOrNull()) |point| {
|
||||||
|
if (map.get(point.x, point.y) != .Empty) continue;
|
||||||
|
|
||||||
|
inline for (neighbours) |neighbour| {
|
||||||
|
const nx: i32 = @as(i32, @intCast(point.x)) + neighbour[0];
|
||||||
|
const ny: i32 = @as(i32, @intCast(point.y)) + neighbour[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)
|
||||||
|
};
|
||||||
|
const next_index = map.getIndex(next_point.x, next_point.y);
|
||||||
|
if (map.get(next_point.x, next_point.y) == .Empty and region_map[next_index] == null) {
|
||||||
|
try stack.append(next_point);
|
||||||
|
region_map[next_index] = region_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isIntersection(map: Map, x: u32, y: u32) bool {
|
||||||
|
inline for (neighbours) |neighbour| {
|
||||||
|
const nx = @as(i32, @intCast(x)) + neighbour[0];
|
||||||
|
const ny = @as(i32, @intCast(y)) + neighbour[1];
|
||||||
|
if (map.get(@intCast(nx), @intCast(ny)) == .Empty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug(self: NodeMap) void {
|
||||||
|
for (0..self.height) |y| {
|
||||||
|
for (0..self.width) |x| {
|
||||||
|
const idx = @as(usize, @intCast(self.width)) * y + x;
|
||||||
|
if (self.region_map[idx]) |region_id| {
|
||||||
|
std.debug.print("{c}", .{ region_id + 'A' });
|
||||||
|
} else {
|
||||||
|
std.debug.print(".", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0.., self.regions) |region_id, region| {
|
||||||
|
std.debug.print("Region {c} ({}):", .{@as(u8, @intCast(region_id)) + 'A', region.size});
|
||||||
|
for (region.connections.constSlice()) |other_region_id| {
|
||||||
|
std.debug.print(" {c}", .{@as(u8, @intCast(other_region_id)) + 'A'});
|
||||||
|
}
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const VisitedRegionBitSet = std.bit_set.ArrayBitSet(usize, 128);
|
||||||
|
const QueueItem = struct {
|
||||||
|
region: NodeRegionId,
|
||||||
|
distance: u32,
|
||||||
|
visited: VisitedRegionBitSet,
|
||||||
|
};
|
||||||
|
|
||||||
|
const WalkPriorityQueue = std.PriorityQueue(QueueItem, void, compareQueueItem);
|
||||||
|
|
||||||
|
fn compareQueueItem(_: void, a: QueueItem, b: QueueItem) std.math.Order {
|
||||||
|
return std.math.order(b.distance, a.distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_longest_path(allocator: Allocator, node_map: NodeMap, start: PointU32, end: PointU32, max_best_distances: ?u32) !u64 {
|
||||||
|
const start_region = node_map.region_map[node_map.width * start.y + start.x].?;
|
||||||
|
const end_region = node_map.region_map[node_map.width * end.y + end.x].?;
|
||||||
|
|
||||||
|
const SeenEntry = struct {
|
||||||
|
visited: VisitedRegionBitSet,
|
||||||
|
};
|
||||||
|
var seen = std.AutoHashMap(SeenEntry, void).init(allocator);
|
||||||
|
defer seen.deinit();
|
||||||
|
|
||||||
|
var queue = WalkPriorityQueue.init(allocator, {});
|
||||||
|
defer queue.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
|
var visited = VisitedRegionBitSet.initEmpty();
|
||||||
|
visited.set(start_region);
|
||||||
|
try queue.add(QueueItem{
|
||||||
|
.distance = node_map.regions[start_region].size - 1,
|
||||||
|
.region = start_region,
|
||||||
|
.visited = visited
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var best_distance: u32 = 0;
|
||||||
|
var best_distance_count: u32 = 0;
|
||||||
|
|
||||||
|
while (queue.removeOrNull()) |item| {
|
||||||
|
const region_id = item.region;
|
||||||
|
if (region_id == end_region) {
|
||||||
|
if (best_distance > item.distance) {
|
||||||
|
best_distance_count += 1;
|
||||||
|
|
||||||
|
// TODO: A hack to make this algorithm finish in a reasonable amount of time for part 2,
|
||||||
|
// The idea is that if the best distance does not change for a while, it is probably the best.
|
||||||
|
// Probably...
|
||||||
|
// This assumption should mostly work and not give false positives, because a PriorityQueue was used.
|
||||||
|
if (best_distance_count == max_best_distances) break;
|
||||||
|
} else {
|
||||||
|
best_distance_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
best_distance = @max(best_distance, item.distance);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const region = node_map.regions[region_id];
|
||||||
|
for (region.connections.constSlice()) |other_region_id| {
|
||||||
|
const other_region = node_map.regions[other_region_id];
|
||||||
|
if (item.visited.isSet(other_region_id)) continue;
|
||||||
|
|
||||||
|
var visited = item.visited;
|
||||||
|
visited.set(other_region_id);
|
||||||
|
const seen_entry = SeenEntry{
|
||||||
|
.visited = visited,
|
||||||
|
};
|
||||||
|
if (seen.contains(seen_entry)) continue;
|
||||||
|
|
||||||
|
try seen.put(seen_entry, {});
|
||||||
|
try queue.add(QueueItem{
|
||||||
|
.distance = item.distance + other_region.size,
|
||||||
|
.region = other_region_id,
|
||||||
|
.visited = visited
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part1(input: *aoc.Input) !aoc.Result {
|
||||||
|
const allocator = input.allocator;
|
||||||
|
const map = try parseInput(allocator, input.lines);
|
||||||
|
defer map.deinit();
|
||||||
|
|
||||||
|
const node_map = try NodeMap.init(allocator, map);
|
||||||
|
defer node_map.deinit();
|
||||||
|
|
||||||
|
const start = PointU32{
|
||||||
|
.x = findEmptySpot(map, 0) orelse return error.NoStart,
|
||||||
|
.y = 0
|
||||||
|
};
|
||||||
|
const end = PointU32{
|
||||||
|
.x = findEmptySpot(map, map.height-1) orelse return error.NoEnd,
|
||||||
|
.y = map.height-1
|
||||||
|
};
|
||||||
|
|
||||||
|
const answer = try find_longest_path(allocator, node_map, start, end, null);
|
||||||
|
|
||||||
|
return .{ .uint = answer };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn part2(input: *aoc.Input) !aoc.Result {
|
||||||
|
const allocator = input.allocator;
|
||||||
|
const map = try parseInput(allocator, input.lines);
|
||||||
|
defer map.deinit();
|
||||||
|
|
||||||
|
const node_map = try NodeMap.init(allocator, map);
|
||||||
|
defer node_map.deinit();
|
||||||
|
|
||||||
|
for (0.., node_map.regions) |region_id, *region| {
|
||||||
|
for (region.connections.constSlice()) |other_region_id| {
|
||||||
|
const other_region = &node_map.regions[other_region_id];
|
||||||
|
if (std.mem.indexOfScalar(NodeRegionId, other_region.connections.constSlice(), @intCast(region_id)) == null) {
|
||||||
|
try other_region.connections.append(@intCast(region_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = PointU32{
|
||||||
|
.x = findEmptySpot(map, 0) orelse return error.NoStart,
|
||||||
|
.y = 0
|
||||||
|
};
|
||||||
|
const end = PointU32{
|
||||||
|
.x = findEmptySpot(map, map.height-1) orelse return error.NoEnd,
|
||||||
|
.y = map.height-1
|
||||||
|
};
|
||||||
|
|
||||||
|
const answer = try find_longest_path(allocator, node_map, end, start, 1000);
|
||||||
|
|
||||||
|
return .{ .uint = answer };
|
||||||
|
}
|
||||||
|
|
||||||
|
const example_input = [_][]const u8{
|
||||||
|
"#.#####################",
|
||||||
|
"#.......#########...###",
|
||||||
|
"#######.#########.#.###",
|
||||||
|
"###.....#.>.>.###.#.###",
|
||||||
|
"###v#####.#v#.###.#.###",
|
||||||
|
"###.>...#.#.#.....#...#",
|
||||||
|
"###v###.#.#.#########.#",
|
||||||
|
"###...#.#.#.......#...#",
|
||||||
|
"#####.#.#.#######.#.###",
|
||||||
|
"#.....#.#.#.......#...#",
|
||||||
|
"#.#####.#.#.#########v#",
|
||||||
|
"#.#...#...#...###...>.#",
|
||||||
|
"#.#.#v#######v###.###v#",
|
||||||
|
"#...#.>.#...>.>.#.###.#",
|
||||||
|
"#####v#.#.###v#.#.###.#",
|
||||||
|
"#.....#...#...#.#.#...#",
|
||||||
|
"#.#########.###.#.#.###",
|
||||||
|
"#...###...#...#...#.###",
|
||||||
|
"###.###.#.###v#####v###",
|
||||||
|
"#...#...#.#.>.>.#.>.###",
|
||||||
|
"#.###.###.#.###.#.#v###",
|
||||||
|
"#.....###...###...#...#",
|
||||||
|
"#####################.#",
|
||||||
|
};
|
||||||
|
|
||||||
|
test "part 1 example" {
|
||||||
|
try aoc.expectAnswerUInt(part1, 94, &example_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "part 2 example" {
|
||||||
|
try aoc.expectAnswerUInt(part2, 154, &example_input);
|
||||||
|
}
|
@ -42,6 +42,8 @@ const Days = [_]aoc.Day{
|
|||||||
create_day(@import("./day19.zig")),
|
create_day(@import("./day19.zig")),
|
||||||
create_day(@import("./day20.zig")),
|
create_day(@import("./day20.zig")),
|
||||||
create_day(@import("./day21.zig")),
|
create_day(@import("./day21.zig")),
|
||||||
|
create_day(@import("./day22.zig")),
|
||||||
|
create_day(@import("./day23.zig")),
|
||||||
};
|
};
|
||||||
|
|
||||||
fn kilobytes(count: u32) u32 {
|
fn kilobytes(count: u32) u32 {
|
||||||
@ -206,4 +208,6 @@ test {
|
|||||||
_ = @import("./day19.zig");
|
_ = @import("./day19.zig");
|
||||||
_ = @import("./day20.zig");
|
_ = @import("./day20.zig");
|
||||||
_ = @import("./day21.zig");
|
_ = @import("./day21.zig");
|
||||||
|
_ = @import("./day22.zig");
|
||||||
|
_ = @import("./day23.zig");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user