From 6e6cd59ea4f3e731dd238e6ef96f44930f0094ff Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sat, 23 Dec 2023 00:31:18 +0200 Subject: [PATCH] solve day 10 --- src/day10.zig | 425 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 2 + 2 files changed, 427 insertions(+) create mode 100644 src/day10.zig diff --git a/src/day10.zig b/src/day10.zig new file mode 100644 index 0000000..3399bf6 --- /dev/null +++ b/src/day10.zig @@ -0,0 +1,425 @@ +const std = @import("std"); +const aoc = @import("./aoc.zig"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const Point = @import("./point.zig").Point(u32); + +const Direction = enum { + Up, + Down, + Left, + Right, + + fn opposite(self: Direction) Direction { + return switch (self) { + .Up => .Down, + .Down => .Up, + .Left => .Right, + .Right => .Left, + }; + } +}; + +const Pipe = [2]Direction; + +const VerticalPipe = Pipe{ .Up , .Down }; +const HorizontalPipe = Pipe{ .Left , .Right }; +const LeftUpPipe = Pipe{ .Left , .Up }; +const LeftDownPipe = Pipe{ .Left , .Down }; +const RightUpPipe = Pipe{ .Right, .Up }; +const RightDownPipe = Pipe{ .Right, .Down }; + +const pipe_variants = .{ + .{ '|', VerticalPipe }, + .{ '-', HorizontalPipe }, + .{ 'J', LeftUpPipe }, + .{ '7', LeftDownPipe }, + .{ 'L', RightUpPipe }, + .{ 'F', RightDownPipe }, +}; +const max_pipe_variants = pipe_variants.len; + +const TileType = enum { Ground, Start, Pipe }; +const Tile = union(TileType) { + Ground: void, + Start: void, + Pipe: Pipe, + + fn to_char(self: Tile) u8 { + return switch (self) { + .Start => 'S', + .Ground => '.', + .Pipe => |dirs| find_pipe_symbol(dirs[0], dirs[1]) orelse unreachable, + }; + } +}; + +const Grid = struct { + allocator: Allocator, + width: u32, + height: u32, + tiles: []Tile, + + fn deinit(self: Grid) void { + self.allocator.free(self.tiles); + } + + fn print(self: *Grid) void { + for (0..self.height) |y| { + for (0..self.width) |x| { + const char = self.get(@intCast(x), @intCast(y)).to_char(); + const symbol = switch(char) { + '.' => " ", + 'S' => "S", + '|' => "│", + '-' => "─", + 'J' => "┘", + '7' => "┐", + 'L' => "└", + 'F' => "┌", + else => unreachable + }; + std.debug.print("{s}", .{symbol}); + } + std.debug.print("\n", .{}); + } + } + + fn get_idx(self: *const Grid, x: u32, y: u32) usize { + return y * self.width + x; + } + + fn get(self: *const Grid, x: u32, y: u32) Tile { + return self.tiles[self.get_idx(x, y)]; + } + + fn set(self: *Grid, x: u32, y: u32, tile: Tile) void { + self.tiles[self.get_idx(x, y)] = tile; + } + + fn in_bounds(self: *const Grid, x: i32, y: i32) bool { + return x >= 0 and y >= 0 and x < self.width and y < self.height; + } +}; + +fn does_pipe_have_direction(pipe: Pipe, dir: Direction) bool { + return pipe[0] == dir or pipe[1] == dir; +} + +fn find_pipe_variant(dir1: Direction, dir2: Direction) ?Pipe { + inline for (pipe_variants) |pipe_variant| { + const variant_dir1 = pipe_variant[1][0]; + const variant_dir2 = pipe_variant[1][1]; + if ((variant_dir1 == dir1 and variant_dir2 == dir2) or (variant_dir1 == dir2 and variant_dir2 == dir1)) { + return pipe_variant[1]; + } + } + return null; +} + +fn find_pipe_symbol(dir1: Direction, dir2: Direction) ?u8 { + inline for (pipe_variants) |pipe_variant| { + const variant_dir1 = pipe_variant[1][0]; + const variant_dir2 = pipe_variant[1][1]; + if ((variant_dir1 == dir1 and variant_dir2 == dir2) or (variant_dir1 == dir2 and variant_dir2 == dir1)) { + return pipe_variant[0]; + } + } + return null; +} + +fn parse_tile(char: u8) !Tile { + const void_value = @as(void, undefined); + + if (char == 'S') { + return Tile{ .Start = void_value }; + } else if (char == '.') { + return Tile{ .Ground = void_value }; + } + + inline for (pipe_variants) |pipe_variant| { + if (pipe_variant[0] == char) { + return Tile { .Pipe = pipe_variant[1] }; + } + } + + return error.UnknownTile; +} + +fn parse_input(allocator: Allocator, lines: []const []const u8) !Grid { + var height: u32 = @intCast(lines.len); + var width: u32 = @intCast(lines[0].len); + + var tiles = try allocator.alloc(Tile, width * height); + errdefer allocator.free(tiles); + + for (0.., lines) |y, line| { + for (0.., line) |x, char| { + var idx = y * width + x; + tiles[idx] = try parse_tile(char); + } + } + + return Grid{ + .allocator = allocator, + .width = width, + .height = height, + .tiles = tiles + }; +} + +fn find_starting_position(grid: *const Grid) ?Point { + for (0..grid.height) |y| { + for (0..grid.width) |x| { + const tile = grid.get(@intCast(x), @intCast(y)); + if (@as(TileType, tile) == TileType.Start) { + return Point{ .x = @intCast(x), .y = @intCast(y) }; + } + } + } + return null; +} + +fn get_neighbours(grid: *const Grid, x: u32, y: u32) std.BoundedArray(Point, 4) { + const offsets = .{ + .{ 0, -1 }, + .{ 0, 1 }, + .{ -1, 0 }, + .{ 1, 0 }, + }; + + var neighbours = std.BoundedArray(Point, 4).init(0) catch unreachable; + inline for (offsets) |offset| { + const ox = offset[0]; + const oy = offset[1]; + + var nx: i32 = @intCast(@as(i32, @intCast(x)) + ox ); + var ny: i32 = @intCast(@as(i32, @intCast(y)) + oy ); + if (grid.in_bounds(nx, ny)) { + neighbours.append(Point{ .x = @intCast(nx), .y = @intCast(ny) }) catch unreachable; + } + } + return neighbours; +} + +fn get_pipe_neighbours(grid: *const Grid, x: u32, y: u32) std.BoundedArray(Point, 4) { + var pipe_neighbours = std.BoundedArray(Point, 4).init(0) catch unreachable; + + const neighbours = get_neighbours(grid, x, y); + const current_tile = grid.get(x, y); + assert(@as(TileType, current_tile) == TileType.Pipe); + const current_pipe = current_tile.Pipe; + + for (neighbours.slice()) |neighbour| { + const tile = grid.get(neighbour.x, neighbour.y); + if (@as(TileType, tile) != TileType.Pipe) continue; + + var direction: Direction = undefined; + if (neighbour.x > x) { + direction = .Right; + } else if (neighbour.x < x) { + direction = .Left; + } else if (neighbour.y > y) { + direction = .Down; + } else if (neighbour.y < y) { + direction = .Up; + } else { + unreachable; + } + + if (!does_pipe_have_direction(current_pipe, direction)) continue; + + const opposite_direction = direction.opposite(); + if (does_pipe_have_direction(tile.Pipe, opposite_direction)) { + pipe_neighbours.append(neighbour) catch unreachable; + } + } + + return pipe_neighbours; +} + +fn get_connections(grid: *const Grid, x: u32, y: u32) std.BoundedArray(Direction, 4) { + const directions = .{ + .{ Direction.Up , 0, -1 }, + .{ Direction.Down , 0, 1 }, + .{ Direction.Left , -1, 0 }, + .{ Direction.Right, 1, 0 }, + }; + + var connections = std.BoundedArray(Direction, 4).init(0) catch unreachable; + inline for (directions) |direction_tuple| { + const direction: Direction = direction_tuple[0]; + const opposite_direction = direction.opposite(); + const ox = direction_tuple[1]; + const oy = direction_tuple[2]; + + var nx: i32 = @intCast(@as(i32, @intCast(x)) + ox ); + var ny: i32 = @intCast(@as(i32, @intCast(y)) + oy ); + if (grid.in_bounds(nx, ny)) { + const tile = grid.get(@intCast(nx), @intCast(ny)); + if (@as(TileType, tile) == TileType.Pipe and does_pipe_have_direction(tile.Pipe, opposite_direction)) { + connections.append(direction) catch unreachable; + } + } + } + return connections; +} + +fn get_next_pipe(grid: *const Grid, x: u32, y: u32, current_path: []Point) ?Point { + var next_pipes = get_pipe_neighbours(grid, x, y); + for (next_pipes.slice()) |point| { + if (current_path.len > 1) { + const first_path_point = current_path[0]; + const prev_path_point = current_path[current_path.len-2]; + if (prev_path_point.eql(&point) or first_path_point.eql(&point)) continue; + } + + return point; + } + + return null; +} + +fn find_loop(allocator: Allocator, grid: *const Grid, start: Point) !std.ArrayList(Point) { + var loop_path = std.ArrayList(Point).init(allocator); + errdefer loop_path.deinit(); + + try loop_path.append(start); + + var current_point = start; + while (true) { + const next_point = get_next_pipe(grid, current_point.x, current_point.y, loop_path.items); + if (next_point == null) break; + + current_point = next_point.?; + try loop_path.append(next_point.?); + } + + return loop_path; +} + +pub fn part1(input: *aoc.Input) !aoc.Result { + const allocator = input.allocator; + var grid = try parse_input(allocator, input.lines); + defer grid.deinit(); + + const start = find_starting_position(&grid) orelse return error.StartNotFound; + const start_connections = get_connections(&grid, start.x, start.y); + assert(start_connections.len == 2); + const pipe_on_start = find_pipe_variant(start_connections.buffer[0], start_connections.buffer[1]) orelse unreachable; + + grid.set(start.x, start.y, Tile{ .Pipe = pipe_on_start }); + + var loop_path = try find_loop(allocator, &grid, start); + defer loop_path.deinit(); + + const loop_length = loop_path.items.len; + + return .{ .uint = loop_length/2 }; +} + +pub fn part2(input: *aoc.Input) !aoc.Result { + const allocator = input.allocator; + var grid = try parse_input(allocator, input.lines); + defer grid.deinit(); + + const start = find_starting_position(&grid) orelse return error.StartNotFound; + const start_connections = get_connections(&grid, start.x, start.y); + assert(start_connections.len == 2); + const pipe_on_start = find_pipe_variant(start_connections.buffer[0], start_connections.buffer[1]) orelse unreachable; + + grid.set(start.x, start.y, Tile{ .Pipe = pipe_on_start }); + + var loop_path = try find_loop(allocator, &grid, start); + defer loop_path.deinit(); + + try loop_path.append(loop_path.items[0]); + + // Shoelace theorum + Pick's theorum + // https://www.reddit.com/r/adventofcode/comments/18evyu9/comment/kcqmhwk/?context=3 + var twice_area: i32 = 0; + for (0..(loop_path.items.len-1)) |i| { + const cur = loop_path.items[i]; + const next = loop_path.items[i+1]; + + const y_0: i32 = @intCast(cur.y); + const x_0: i32 = @intCast(cur.x); + const y_1: i32 = @intCast(next.y); + const x_1: i32 = @intCast(next.x); + + twice_area += (x_0 * y_1) - (y_0 * x_1); + } + + var result = std.math.absCast(twice_area) / 2 - (loop_path.items.len / 2 - 1); + + return .{ .uint = result }; +} + +test "part 1 example 1" { + const example_input = [_][]const u8{ + ".....", + ".S-7.", + ".|.|.", + ".L-J.", + ".....", + }; + try aoc.expectAnswerUInt(part1, 4, &example_input); +} + +test "part 1 example 2" { + const example_input = [_][]const u8{ + "..F7.", + ".FJ|.", + "SJ.L7", + "|F--J", + "LJ...", + }; + try aoc.expectAnswerUInt(part1, 8, &example_input); +} + +test "part 2 example 1" { + const example_input = [_][]const u8{ + "...........", + ".S-------7.", + ".|F-----7|.", + ".||.....||.", + ".||.....||.", + ".|L-7.F-J|.", + ".|..|.|..|.", + ".L--J.L--J.", + "...........", + }; + try aoc.expectAnswerUInt(part2, 4, &example_input); +} + +test "part 2 example 2" { + const example_input = [_][]const u8{ + ".F----7F7F7F7F-7....", + ".|F--7||||||||FJ....", + ".||.FJ||||||||L7....", + "FJL7L7LJLJ||LJ.L-7..", + "L--J.L7...LJS7F-7L7.", + "....F-J..F7FJ|L7L7L7", + "....L7.F7||L7|.L7L7|", + ".....|FJLJ|FJ|F7|.LJ", + "....FJL-7.||.||||...", + "....L---J.LJ.LJLJ...", + }; + try aoc.expectAnswerUInt(part2, 8, &example_input); +} + +test "part 2 example 3" { + const example_input = [_][]const u8{ + "FF7FSF7F7F7F7F7F---7", + "L|LJ||||||||||||F--J", + "FL-7LJLJ||||||LJL-77", + "F--JF--7||LJLJ7F7FJ-", + "L---JF-JLJ.||-FJLJJ7", + "|F|F-JF---7F7-L7L|7|", + "|FFJF7L7F-JF7|JL---7", + "7-L-JL7||F7|L7F-7F7|", + "L.L7LFJ|||||FJL7||LJ", + "L7JLJL-JLJLJL--JLJ.L", + }; + try aoc.expectAnswerUInt(part2, 10, &example_input); +} diff --git a/src/main.zig b/src/main.zig index 0b9b452..eb2434a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -30,6 +30,7 @@ const Days = [_]aoc.Day{ create_day(@import("day7.zig")), create_day(@import("day8.zig")), create_day(@import("day9.zig")), + create_day(@import("day10.zig")), }; fn kilobytes(count: u32) u32 { @@ -183,4 +184,5 @@ test { _ = @import("day7.zig"); _ = @import("day8.zig"); _ = @import("day9.zig"); + _ = @import("day10.zig"); }