# Day 16 ```elixir Mix.install([:kino_aoc, :image]) ``` ## Setup ```elixir {:ok, puzzle_input} = KinoAOC.download_puzzle("2024", "16", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION")) ``` ``` {:ok, "#############################################################################################################################################\n#.....#.......#.............#.........#.......................#.......#...#...............#.....#.#.................................#.#....E#\n#.#####.###.###.#######.###.#.#.#######.#.###.#####.###.#######.#.###.###.#.#####.#####.###.#.#.#.#.#####.###.#.###.#######.###.#.#.#.#.#.#.#\n#.......#...#...#.....#.#.#.#.#.#...#...#.....#...#.#...#.......#...#.#...#...#...#.#...#...............#...#...#.#.......#.#...#.#.#...#.#.#\n#########.#.#.###.#####.#.#.#.#.#.#.#.#########.#.#.###.#.#########.#.#.#.#.###.#.#.#.###.###.#####.###.###.#.#.#.###.###.#.#.###.#.#.###.#.#\n#.......#.#.#.#...#...#.#.#.#.#...#.....#.......#.#...#.#.#...#...#.#...#.#.#...#.....#...#.#...#...#.....#.#.....#.....#.#...#...#.#.#...#.#\n#.#####.#.###.#.#.#.#.#.#.#.###########.#######.#.###.#.#.###.#.#.#.#.#####.#.###.###.#.###.###.#.###.#.###.#.#.#.#.#.#.#.#.#.#.###.###.#####\n#...#...#.......#.#.#...#.#...........................#.#.#...#.#...#.#.....#.#.....#.#...#...#.....................#.#.#...................#\n#.#.#.#######.#####.#####.###########.#######.#####.#.###.#.###.#######.#####.###.#.#.###.#.#.#.#.#######.###.#.#####.#.#.#.#.###.###.#.###.#\n#.#...#.....#.#...#...#.......#.......#.....#...#...#.....#.#...#.....#...#...#.....#...#...#.#...#.......#...#.#...#.#.#.#.#...#.#...#...#.#\n#.#.###.###.###.#.#.#.###.###.#.#.#####.###.###.#.#########.#.###.###.###.#.###.#.###.#######.#####.#######.#.#.#.#.#.###.###.#.#.#.#######.#\n#.#.....#.......#.#.#...#.#...#.#...#...#.#.#.#.#.......#...#...#...#.......................#...#.#.#.....#.#.....#.#.........#...#.#.....#.#\n#.###.###########.#####.#.#####.#.#.#.#.#.#.#.#.###.###.#.#.###.###.#########.###.#.#.#.###.#.#.#.#.#####.#.#.#.#####.#######.#####.#.###.#.#\n#...#...................#.....#.#.#.#.#.#.....#.....#.#...#.#.#...#.....#...#.....#.#.#.#.#.....#.#.....#.#...#.#.........#...........#.....#\n#.#.#.#.###.#.###############.#.#.#.#.#.#############.#####.#.###.#####.#.#.#####.#.#.#.#.#######.#####.#.###.#.#.###.###.#.#.###.###########\n#.#.#.#.#.#.#.#.....#.......#.#...#...#...............#...#...#.#...#...#.#.......#.#.#.#.......#...#...#.....#.#.#.......#.#...#.#.........#\n#.#.###.#.#.###.###.###.#.#.#.###.#.#####.###.###.###.#.#.###.#.#.###.###.###.###.#.#.#.###.###.###.#.###.#####.#.#.#.###.#.#.#.###.#######.#\n#.#.....#.#...#.#.#...#.#.#.#...#.#.#...#...#...#...#...#...#...#.....#...#...#.......#.....#.#.....#...#.......#...#.#...#...#...#.#.#.....#\n#.#######.###.#.#.###.#.#.#.#.#.#.###.#.#####.#.#.#.#########.#.#######.#.#.#.#.#############.#####.###.#########.#####.###.#####.#.#.#.#.###\n#.#.......#.#.....#...#.#.#...#.#.#...#.....#.#.#.#.........#.#.........#.#.#.#.#.................#.#.#...#.......#.........#...#...#.#.....#\n#.#.#####.#.#######.#####.#######.#.#####.#.###.#.#########.#.#.#########.#.#.#.###.#######.#.#####.#.###.#.#######.#.###.###.#.#####.###.#.#\n#.#.....#.......#...#...#...#.....#.#...........#.#.......#...#.#...#.....#.#.#...#.#.......#.#...#...#...#.....#...#...#.#.................#\n#.#####.#######.#.###.#.###.#.#####.#######.#####.#.#.###.#####.#.#.###.###.#.###.###.#####.###.#.###.#.#######.#.#####.#.#####.#.#.###.#.#.#\n#.#...#.#.....#...#...#...#.#.......#.....#.#.....#.#...#.#...#.#.#...#.#...#.#.#...#.#...#.#...#...#.#.........#.....#.#.......#...#...#.#.#\n#.#.#.#.#.###.#.###.#####.#.#.#####.#.###.###.#########.###.#.#.#.###.#.#.###.#.###.#.#.#.###.###.###.###############.#.###########.#.###.#.#\n#.#.#.#.#.#.....#...#...#...#.....#...#.#...#.#...#...#.#...#.#...#...#.#.#.#.#.........#.#...#.#.....#.......#.......#.........#...#.#.....#\n#.#.#.#.#.#######.###.#.#.#.#####.###.#.###.#.#.#.#.#.#.#.###.#####.#####.#.#.#########.#.#.###.#######.#######.###############.#.###.#.#.###\n#.#.#...#.........#...#.#.#.....#...#...#.#.....#...#...#.#.#...#...#.....#.#.....#.....#...#.....#.....#.....#...#...#...#.....#...#.#.#...#\n#.#.#########.#######.#.#.#.###.###.###.#.###########.###.#.###.#.###.#.###.#####.#.#########.#.#.###.#.#.###.#.#.#.#.#." <> ...} ``` ```elixir %{?# => walls, ?S => [start], ?E => [finish]} = puzzle_input |> String.trim() |> String.split("\n", trim: true) |> Enum.with_index() |> Enum.flat_map(fn {row, y} -> row |> String.trim() |> String.to_charlist() |> Enum.with_index() |> Enum.map(fn {char, x} -> {{x, y}, char} end) end) |> Enum.group_by(&elem(&1, 1), &elem(&1, 0)) walls = MapSet.new(walls) ``` ``` MapSet.new([ {76, 13}, {112, 138}, {38, 2}, {124, 56}, {83, 76}, {140, 11}, {100, 134}, {75, 140}, {103, 106}, {68, 134}, {124, 60}, {35, 30}, {2, 132}, {8, 50}, {78, 98}, {101, 62}, {98, 136}, {95, 56}, {74, 12}, {102, 74}, {22, 38}, {14, 86}, {12, 135}, {86, 10}, {29, 26}, {4, 81}, {31, 42}, {9, 34}, {137, 16}, {86, 138}, {90, 0}, {14, 122}, {120, 42}, {102, 57}, {84, 102}, {138, 124}, {0, 101}, {116, 96}, {54, 138}, {18, 134}, {82, 60}, {15, 92}, {58, 58}, {78, 75}, {75, 0}, {16, 73}, {76, 2}, {58, 84}, {138, ...}, {...}, ... ]) ``` ```elixir {_, {w, h}} = Enum.min_max(walls) w = w + 1 h = h + 1 ``` ``` 141 ``` ```elixir put_in(%{}, [Access.key(:a, %{}), :b], 10) ``` ``` %{a: %{b: 10}} ``` ```elixir defmodule Race.B do @turns ~w'^ > v <'a defguardp score(elements, pos, dir) when :erlang.map_get({pos, dir}, elements).score def search_paths(start, finish, dir \\ :>, walls) when dir in @turns do result = do_search([{0, [{start, dir}]}], walls, %{}) %{paths: visited, score: score} = result[{finish, :>}] all = get_all(visited, result) {score, all} end def get_all(path, seen) do Enum.reduce(path, MapSet.new(), fn p, acc -> path = MapSet.new(seen[p].paths, fn {p, _} -> p end) MapSet.union(acc, path) end) end def do_search([], _, visited), do: visited def do_search([{cost, [{curr, dir} | _] = path} | rest], walls, visited) when cost == score(visited, curr, dir) do visited = Map.update!(visited, {curr, dir}, &%{&1 | paths: path}) do_search(rest, walls, visited) end def do_search([{cost, [{curr, prev} | _] = path} | rest], walls, visited) when not is_map_key(visited, {curr, prev}) when cost < score(visited, curr, prev) do visited = Map.put(visited, {curr, prev}, %{score: cost, paths: path}) @turns |> Enum.map(fn dir -> {cost + cost(prev, dir), [{step(curr, dir), dir} | path]} end) |> Enum.reject(fn {_cost, [{pos, _} | _]} -> pos in walls end) |> sort_merge(rest) |> do_search(walls, visited) end def do_search([{_, [_curr | _]} | rest], walls, visited), do: do_search(rest, walls, visited) # Going straight is cheap and turning is costly defp cost(a, a), do: 1 defp cost(_, _), do: 1001 defp step({x, y}, :^), do: {x, y - 1} defp step({x, y}, :>), do: {x + 1, y} defp step({x, y}, :v), do: {x, y + 1} defp step({x, y}, :<), do: {x - 1, y} defp sort_merge([], bs), do: bs defp sort_merge(as, []), do: as defp sort_merge([a | as], [b | bs]) do if a < b do [a | sort_merge(as, [b | bs])] else [b | sort_merge([a | as], bs)] end end end ``` ``` {:module, Race.B, <<70, 79, 82, 49, 0, 0, 23, ...>>, {:sort_merge, 2}} ``` ```elixir {score, paths} = Race.B.search_paths(start, finish, walls) ``` ``` {134588, MapSet.new([ {77, 129}, {17, 138}, {17, 137}, {37, 139}, {19, 137}, {100, 131}, {139, 68}, {14, 139}, {79, 128}, {19, 138}, {50, 137}, {139, 67}, {137, 104}, {137, 117}, {131, 49}, {107, 125}, {3, 127}, {135, 64}, {4, 131}, {21, 134}, {63, 127}, {101, 124}, {5, 139}, {125, 14}, {129, 48}, {139, 70}, {21, 119}, {17, 129}, {137, 120}, {17, 136}, {131, 120}, {32, 139}, {133, 21}, {102, 123}, {12, 111}, {123, 19}, {112, 127}, {73, 129}, {7, 116}, {127, 22}, {7, 137}, {9, 131}, {24, 133}, {137, 95}, {8, 133}, {134, 7}, {129, ...}, {...}, ... ])} ``` ## Part 1 ```elixir score ``` ``` 134588 ``` ```elixir Image.new!(w, h) |> Image.mutate(fn img -> for {x, y} <- walls do Image.Draw.point(img, x, y, color: [128, 0, 0]) end for {x, y} <- paths do Image.Draw.point(img, x, y, color: [0, 255, 0]) end img end) |> elem(1) |> Image.resize!(6, interpolate: :nearest) ``` ## Part 2 ```elixir MapSet.size(paths) ``` ``` 625 ```