solve day 10
This commit is contained in:
parent
6c5e1ea117
commit
6e6cd59ea4
425
src/day10.zig
Normal file
425
src/day10.zig
Normal 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);
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user