summaryrefslogtreecommitdiff
path: root/2015/day9.exs
blob: d50b51b94f0752294592a67650e395e5c26650c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
edgesText = File.stream!("inputs/day9.txt")

costs =
  Enum.reduce(edgesText, %{}, fn line, acc ->
    [_, a, b, distance] = Regex.run(~r/(\w+) to (\w+) = (\d+)/, line)
    {distance, ""} = Integer.parse(distance)

    {_, newAcc} =
      Map.get_and_update(acc, a, fn
        nil -> {nil, %{b => distance}}
        aToBeDistances -> {aToBeDistances, Map.put(aToBeDistances, b, distance)}
      end)

    {_, newAcc} =
      Map.get_and_update(newAcc, b, fn
        nil -> {nil, %{a => distance}}
        bToADistances -> {bToADistances, Map.put(bToADistances, a, distance)}
      end)

    newAcc
  end)

defmodule MemoizedTravellingSanta do
  use Agent

  def start do
    Agent.start_link(fn -> %{} end, name: __MODULE__)
  end

  def findShortestPath(costs) do
    pathCosts =
      Enum.map(costs, fn {start, _} ->
        shortestStartingHere =
          MemoizedTravellingSanta.findShortestPath(costs, MapSet.new([start]), start)

        shortestStartingHere
      end)

    min =
      Enum.map(pathCosts, fn {min, _} -> min end)
      |> Enum.min()

    max =
      Enum.map(pathCosts, fn {_, max} -> max end)
      |> Enum.max()

    {min, max}
  end

  def findShortestPath(costs, visited, current) do
    if map_size(costs) == MapSet.size(visited) do
      {0, 0}
    else
      cached_value = Agent.get(__MODULE__, &Map.get(&1, {costs, visited, current}))

      if cached_value do
        cached_value
      else
        leavingCosts = Map.get(costs, current, %{})

        pathCosts =
          Enum.filter(leavingCosts, fn {next, _} -> !MapSet.member?(visited, next) end)
          |> Enum.map(fn {next, cost} ->
            {minSubcost, maxSubcost} =
              MemoizedTravellingSanta.findShortestPath(
                costs,
                MapSet.put(visited, next),
                next
              )

            {cost + minSubcost, cost + maxSubcost}
          end)

        min =
          Enum.map(pathCosts, fn {min, _} -> min end)
          |> Enum.min()

        max =
          Enum.map(pathCosts, fn {_, max} -> max end)
          |> Enum.max()

        result = {min, max}
        Agent.update(__MODULE__, &Map.put(&1, {costs, visited, current}, result))
        result
      end
    end
  end
end

{:ok, _} = MemoizedTravellingSanta.start()

{min, max} = MemoizedTravellingSanta.findShortestPath(costs)

IO.puts("Shortest distance: #{min}")
IO.puts("Longest distance: #{max}")