Powered by AppSignal & Oban Pro

Day 9 - Advent of Code 2015

lib/advent_of_code/2015/day-09.livemd

Day 9 - Advent of Code 2015

Mix.install([:kino, :benchee])
:ok

Links

Prompt

— Day 9: All in a Single Night —

Every year, Santa manages to deliver all of his presents in a single night.

This year, however, he has some new locations to visit; his elves have provided him the distances between every pair of locations. He can start and end at any two (different) locations he wants, but he must visit each location exactly once. What is the shortest distance he can travel to achieve this?

For example, given the following distances:

London to Dublin = 464
London to Belfast = 518
Dublin to Belfast = 141

The possible routes are therefore:

Dublin -> London -> Belfast = 982
London -> Dublin -> Belfast = 605
London -> Belfast -> Dublin = 659
Dublin -> Belfast -> London = 659
Belfast -> Dublin -> London = 605
Belfast -> London -> Dublin = 982

The shortest of these is London -> Dublin -> Belfast = 605, and so the answer is 605 in this example.

What is the distance of the shortest route?

To begin, get your puzzle input.

— Part Two —

The next year, just to show off, Santa decides to take the route with the longest distance instead.

He can still start and end at any two (different) locations he wants, and he still must visit each location exactly once.

For example, given the distances above, the longest route would be 982 via (for example) Dublin -> London -> Belfast.

What is the distance of the longest route?

Although it hasn’t changed, you can still get your puzzle input.

Input

input = Kino.Input.textarea("Please paste your input file:")
input = input |> Kino.Input.read()
"Faerun to Tristram = 65\nFaerun to Tambi = 129\nFaerun to Norrath = 144\nFaerun to Snowdin = 71\nFaerun to Straylight = 137\nFaerun to AlphaCentauri = 3\nFaerun to Arbre = 149\nTristram to Tambi = 63\nTristram to Norrath = 4\nTristram to Snowdin = 105\nTristram to Straylight = 125\nTristram to AlphaCentauri = 55\nTristram to Arbre = 14\nTambi to Norrath = 68\nTambi to Snowdin = 52\nTambi to Straylight = 65\nTambi to AlphaCentauri = 22\nTambi to Arbre = 143\nNorrath to Snowdin = 8\nNorrath to Straylight = 23\nNorrath to AlphaCentauri = 136\nNorrath to Arbre = 115\nSnowdin to Straylight = 101\nSnowdin to AlphaCentauri = 84\nSnowdin to Arbre = 96\nStraylight to AlphaCentauri = 107\nStraylight to Arbre = 14\nAlphaCentauri to Arbre = 46"

Solution

defmodule Day09 do
  defdelegate parse(input), to: __MODULE__.Input

  def part1(input) do
    route_distances(input) |> Enum.min()
  end

  def part2(input) do
    route_distances(input) |> Enum.max()
  end

  def route_distances(input) do
    map = parse(input)
    cities = Map.keys(map)

    Enum.map(cities, fn city ->
      visit_nodes(
        city,
        map,
        {
          [{city, 0}],
          cities |> MapSet.new() |> MapSet.delete(city)
        }
      )
    end)
    |> flatten()
    |> Enum.map(fn route -> Enum.map(route, &elem(&1, 1)) |> Enum.sum() end)
  end

  def visit_nodes(city, map, acc) do
    map[city]
    |> Enum.map(fn {city, distance} ->
      visit_node({city, distance}, map, acc)
    end)
  end

  defp visit_node({city, distance}, map, {routes, available}) do
    cond do
      MapSet.member?(available, city) ->
        visit_nodes(
          city,
          map,
          {
            [{city, distance} | routes],
            MapSet.delete(available, city)
          }
        )

      MapSet.size(available) == 0 ->
        routes

      true ->
        []
    end
  end

  def flatten([[tuple | _] | _] = list) when is_tuple(tuple) do
    list |> Stream.uniq() |> Enum.map(&Enum.reverse/1)
  end

  def flatten(list) when is_list(list) do
    list |> Enum.flat_map(& &1) |> flatten()
  end

  defmodule Input do
    def parse(input) when is_binary(input) do
      input
      |> String.split("\n")
      |> parse()
    end

    def parse(input) when is_list(input) do
      input
      |> Stream.map(&parse_line/1)
      |> Enum.reduce(%{}, fn [from, to, distance], map ->
        map
        |> update_map(from, to, distance)
        |> update_map(to, from, distance)
      end)
    end

    defp update_map(map, a, b, distance) do
      distance = String.to_integer(distance)

      Map.update(map, a, %{b => distance}, &Map.put(&1, b, distance))
    end

    def parse_line(line) do
      line
      |> String.split([" to ", " = "])
      |> Enum.map(&String.trim/1)
    end
  end
end
{:module, Day09, <<70, 79, 82, 49, 0, 0, 17, ...>>,
 {:module, Day09.Input, <<70, 79, 82, ...>>, {:parse_line, 1}}}
map = Day09.parse(input)
%{
  "AlphaCentauri" => %{
    "Arbre" => 46,
    "Faerun" => 3,
    "Norrath" => 136,
    "Snowdin" => 84,
    "Straylight" => 107,
    "Tambi" => 22,
    "Tristram" => 55
  },
  "Arbre" => %{
    "AlphaCentauri" => 46,
    "Faerun" => 149,
    "Norrath" => 115,
    "Snowdin" => 96,
    "Straylight" => 14,
    "Tambi" => 143,
    "Tristram" => 14
  },
  "Faerun" => %{
    "AlphaCentauri" => 3,
    "Arbre" => 149,
    "Norrath" => 144,
    "Snowdin" => 71,
    "Straylight" => 137,
    "Tambi" => 129,
    "Tristram" => 65
  },
  "Norrath" => %{
    "AlphaCentauri" => 136,
    "Arbre" => 115,
    "Faerun" => 144,
    "Snowdin" => 8,
    "Straylight" => 23,
    "Tambi" => 68,
    "Tristram" => 4
  },
  "Snowdin" => %{
    "AlphaCentauri" => 84,
    "Arbre" => 96,
    "Faerun" => 71,
    "Norrath" => 8,
    "Straylight" => 101,
    "Tambi" => 52,
    "Tristram" => 105
  },
  "Straylight" => %{
    "AlphaCentauri" => 107,
    "Arbre" => 14,
    "Faerun" => 137,
    "Norrath" => 23,
    "Snowdin" => 101,
    "Tambi" => 65,
    "Tristram" => 125
  },
  "Tambi" => %{
    "AlphaCentauri" => 22,
    "Arbre" => 143,
    "Faerun" => 129,
    "Norrath" => 68,
    "Snowdin" => 52,
    "Straylight" => 65,
    "Tristram" => 63
  },
  "Tristram" => %{
    "AlphaCentauri" => 55,
    "Arbre" => 14,
    "Faerun" => 65,
    "Norrath" => 4,
    "Snowdin" => 105,
    "Straylight" => 125,
    "Tambi" => 63
  }
}

What is the distance of the shortest route?

Your puzzle answer was 117.

Day09.part1(input)
117

What is the distance of the longest route?

Your puzzle answer was 909.

Day09.part2(input)
909

Both parts of this puzzle are complete! They provide two gold stars: **

At this point, you should return to your Advent calendar and try another puzzle.

If you still want to see it, you can get your puzzle input.

Tests

ExUnit.start(auto_run: false)

defmodule Day09Test do
  use ExUnit.Case, async: false

  setup_all do
    [
      input: "London to Dublin = 464\nLondon to Belfast = 518\nDublin to Belfast = 141"
    ]
  end

  describe "part1/1" do
    test "returns expected value", %{input: input} do
      assert Day09.part1(input) == 605
    end
  end

  describe "part2/1" do
    test "returns expected value", %{input: input} do
      assert Day09.part2(input) == 982
    end
  end
end

ExUnit.run()
..
Finished in 0.00 seconds (0.00s async, 0.00s sync)
2 tests, 0 failures

Randomized with seed 739165
%{excluded: 0, failures: 0, skipped: 0, total: 2}

Benchmarking

# https://github.com/bencheeorg/benchee

Benchee.run(%{
  "Part 1" => fn -> Day09.part1(input) end,
  "Part 2" => fn -> Day09.part2(input) end
})

nil
Operating System: macOS
CPU Information: Apple M1 Pro
Number of Available Cores: 10
Available memory: 16 GB
Elixir 1.14.1
Erlang 25.1.2

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 14 s

Benchmarking Part 1 ...
Benchmarking Part 2 ...

Name             ips        average  deviation         median         99th %
Part 1          4.84      206.78 ms     ±4.73%      208.14 ms      224.22 ms
Part 2          4.71      212.18 ms     ±4.72%      212.25 ms      234.02 ms

Comparison: 
Part 1          4.84
Part 2          4.71 - 1.03x slower +5.40 ms
nil