defmodule Day06 do def parse_data(data) do grid = data |> String.split("\n", trim: true) guard = grid |> map_elements("^") |> hd() obstacles = grid |> map_elements("#") obstacles_vert = obstacles |> Enum.group_by(fn {i, _} -> i end, fn {_, j} -> j end) |> Enum.map(fn {k, v} -> {k, v |> :gb_sets.from_list()} end) |> Map.new() obstacles_hori = obstacles |> Enum.group_by(fn {_, j} -> j end, fn {i, _} -> i end) |> Enum.map(fn {k, v} -> {k, v |> :gb_sets.from_list()} end) |> Map.new() grid = grid |> Enum.map(& :array.from_list(String.codepoints(&1))) |> :array.from_list() {guard, grid, obstacles_vert, obstacles_hori} end def map_elements(grid, element) do for {line, i} <- Enum.with_index(grid), {v, j} <- Enum.with_index(String.codepoints(line)) do if v == element do {i, j} end end |> Enum.filter(& &1 != nil) end def part1({guard, grid, _, _}) do path(guard, :up, grid) |> Enum.map(fn {i, j, _} -> {i, j} end) |> Enum.uniq() |> Enum.count() end def path(acc \\ [], {i, j}, dir, grid) do acc = [{i, j, dir} | acc] {ni, nj, ndir} = next(i, j, dir) case check(ni, nj, grid) do :invalid -> acc :obstacle -> path(tl(acc), {i, j}, ndir, grid) :valid -> path(acc, {ni, nj}, dir, grid) end end def next(i, j, dir) do case dir do :up -> {i-1, j, :right} :right -> {i, j+1, :down} :down -> {i+1, j, :left} :left -> {i, j-1, :up} end end def check(i, j, grid, extra \\ nil) do cond do i < 0 or i == :array.size(grid) or j < 0 or j == :array.get(i, grid) |> :array.size() -> :invalid :array.get(j, :array.get(i, grid)) == "#" or {i, j} == extra -> :obstacle true -> :valid end end def part2({guard, grid, overt, ohori}) do path(guard, :up, grid) |> Enum.map(fn {i, j, _} -> {i, j} end) |> Enum.uniq() |> Enum.filter(& &1 != guard) |> Enum.map(fn {i, j} -> is_loop?(guard, :up, grid, update(overt, i, j), update(ohori, j, i)) end) |> Enum.count(& &1) end def update(m, k, v) do Map.get_and_update(m, k, fn set -> case set do nil -> {set, :gb_sets.from_list([v])} set -> {set, :gb_sets.add(v, set)} end end) |> elem(1) end def next(i, j, dir, overt, ohori) do case dir do :up -> case :gb_sets.smaller(i, Map.get(ohori, j, :gb_sets.empty())) do :none -> :out {_, v} -> {v+1, j, :right} end :right -> case :gb_sets.larger(j, Map.get(overt, i, :gb_sets.empty())) do :none -> :out {_, v} -> {i, v-1, :down} end :down -> case :gb_sets.larger(i, Map.get(ohori, j, :gb_sets.empty())) do :none -> :out {_, v} -> {v-1, j, :left} end :left -> case :gb_sets.smaller(j, Map.get(overt, i, :gb_sets.empty())) do :none -> :out {_, v} -> {i, v+1, :up} end end end def is_loop?(cache \\ MapSet.new(), {i, j}, dir, grid, overt, ohori) do key = {i, j, dir} if MapSet.member?(cache, key) do true else case next(i, j, dir, overt, ohori) do :out -> false {ni, nj, ndir} -> is_loop?(MapSet.put(cache, key), {ni, nj}, ndir, grid, overt, ohori) end end end end data = IO.read(:stdio, :eof) |> Day06.parse_data() {time1 , ans1} = :timer.tc(fn -> Day06.part1(data) end) IO.puts("Time : #{time1 / 1000000}") IO.puts("Answer: #{ans1}") {time2 , ans2} = :timer.tc(fn -> Day06.part2(data) end) IO.puts("Time : #{time2 / 1000000}") IO.puts("Answer: #{ans2}")