solve day 10

This commit is contained in:
Rokas Puzonas 2023-12-23 00:31:18 +02:00
parent 6c5e1ea117
commit 6e6cd59ea4
2 changed files with 427 additions and 0 deletions

425
src/day10.zig Normal file
View File

@ -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);
}

View File

@ -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");
}