|
| 1 | +defmodule AdventOfCode.Y2016.Day13 do |
| 2 | + @moduledoc """ |
| 3 | + --- Day 13: A Maze of Twisty Little Cubicles --- |
| 4 | + Problem Link: https://adventofcode.com/2016/day/13 |
| 5 | + Difficulty: m |
| 6 | + Tags: graph implicit-graph shortest-path a-star bfs |
| 7 | + """ |
| 8 | + import Bitwise |
| 9 | + alias AdventOfCode.Helpers.InputReader |
| 10 | + alias Yog.Pathfinding.AStar |
| 11 | + alias Yog.Traversal |
| 12 | + |
| 13 | + def input, do: InputReader.read_from_file(2016, 13) |
| 14 | + |
| 15 | + def run(input \\ input()) do |
| 16 | + fav = parse(input) |
| 17 | + |
| 18 | + {run_1(fav), run_2(fav)} |
| 19 | + end |
| 20 | + |
| 21 | + defp run_1(fav) do |
| 22 | + # Target coordinate: (31, 39) |
| 23 | + # Finding shortest path using A* search from (1, 1). |
| 24 | + target = {31, 39} |
| 25 | + |
| 26 | + successors = fn pos -> |
| 27 | + open_neighbors(pos, fav) |> Enum.map(fn n -> {n, 1} end) |
| 28 | + end |
| 29 | + |
| 30 | + heuristic = fn {x, y} -> |
| 31 | + abs(x - target_x(target)) + abs(y - target_y(target)) |
| 32 | + end |
| 33 | + |
| 34 | + {:ok, dist} = |
| 35 | + AStar.implicit_a_star( |
| 36 | + from: {1, 1}, |
| 37 | + is_goal: fn pos -> pos == target end, |
| 38 | + successors_with_cost: successors, |
| 39 | + heuristic: heuristic |
| 40 | + ) |
| 41 | + |
| 42 | + dist |
| 43 | + end |
| 44 | + |
| 45 | + defp run_2(fav) do |
| 46 | + # BFS to find all unique nodes reachable within 50 steps. |
| 47 | + Traversal.implicit_fold( |
| 48 | + from: {1, 1}, |
| 49 | + using: :breadth_first, |
| 50 | + initial: MapSet.new(), |
| 51 | + successors_of: fn pos -> open_neighbors(pos, fav) end, |
| 52 | + with: fn acc, pos, meta -> |
| 53 | + new_acc = MapSet.put(acc, pos) |
| 54 | + |
| 55 | + # Stop exploring depth > 50. |
| 56 | + action = if meta.depth >= 50, do: :stop, else: :continue |
| 57 | + {action, new_acc} |
| 58 | + end |
| 59 | + ) |
| 60 | + |> MapSet.size() |
| 61 | + end |
| 62 | + |
| 63 | + defp is_wall?(x, y, fav) do |
| 64 | + if x < 0 or y < 0 do |
| 65 | + true |
| 66 | + else |
| 67 | + val = x * x + 3 * x + 2 * x * y + y + y * y + fav |
| 68 | + rem(count_ones(val), 2) != 0 |
| 69 | + end |
| 70 | + end |
| 71 | + |
| 72 | + defp open_neighbors({x, y}, fav) do |
| 73 | + [{x + 1, y}, {x - 1, y}, {x, y + 1}, {x, y - 1}] |
| 74 | + |> Enum.filter(fn {nx, ny} -> not is_wall?(nx, ny, fav) end) |
| 75 | + end |
| 76 | + |
| 77 | + defp count_ones(0), do: 0 |
| 78 | + defp count_ones(n), do: (n &&& 1) + count_ones(n >>> 1) |
| 79 | + |
| 80 | + defp target_x({x, _}), do: x |
| 81 | + defp target_y({_, y}), do: y |
| 82 | + |
| 83 | + def parse(data \\ input()) do |
| 84 | + data |> String.trim() |> String.trim_trailing(".") |> String.to_integer() |
| 85 | + end |
| 86 | +end |
0 commit comments