From 13a6c53fa677b33de42de6ad36245123a26fe1e6 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sat, 1 Jun 2024 17:03:33 +0300 Subject: [PATCH] solve day 22 --- src/day22.zig | 365 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 2 + 2 files changed, 367 insertions(+) create mode 100644 src/day22.zig diff --git a/src/day22.zig b/src/day22.zig new file mode 100644 index 0000000..5f11fa6 --- /dev/null +++ b/src/day22.zig @@ -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); +} diff --git a/src/main.zig b/src/main.zig index 5796ec7..aafc478 100644 --- a/src/main.zig +++ b/src/main.zig @@ -42,6 +42,7 @@ const Days = [_]aoc.Day{ create_day(@import("./day19.zig")), create_day(@import("./day20.zig")), create_day(@import("./day21.zig")), + create_day(@import("./day22.zig")), }; fn kilobytes(count: u32) u32 { @@ -206,4 +207,5 @@ test { _ = @import("./day19.zig"); _ = @import("./day20.zig"); _ = @import("./day21.zig"); + _ = @import("./day22.zig"); }