Powered by AppSignal & Oban Pro

Advent of Code 2015 Day 9 Part 2

2015_day9_part2.livemd

Advent of Code 2015 Day 9 Part 2

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

Get Inputs

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2015", "9", System.fetch_env!("LB_SESSION"))

My answer

defmodule TSP do
  def cities(distances) do
    distances
    |> Map.keys()
    |> Enum.map(&Tuple.to_list(&1))
    |> Enum.concat()
    |> Enum.uniq()
  end

  def all_routes([]), do: [[]]
  def all_routes(list) do
    for elem <- list,
        rest <- all_routes(list--[elem]) do
      [elem | rest]
    end
  end

  def drop_reversed(list) do
    list
    |> Enum.reduce([], fn new, acc ->
      if Enum.member?(acc, Enum.reverse(new)) do
        acc
      else
        [new | acc]
      end
    end)
  end

  def reverse_distances(distances) do
    distances
    |> Enum.into(%{}, fn {{from, to}, value} ->
      {{to, from}, value}
    end)
  end

  def get_max_distance(permutations, full_distances) do
    permutations
    |> Enum.map(fn permutation ->
      next_permutation = tl(permutation) ++ [nil]
    
      Enum.zip(permutation, next_permutation)
      |> Enum.map(fn {from, to} ->
        if is_nil(to), do: 0, else: Map.get(full_distances, {from, to})
      end)
      |> Enum.sum()
    end)
    |> Enum.max()
  end

  def solve(distances) do
    permutations =
      distances
      |> cities()
      |> all_routes()
      |> drop_reversed()

    full_distances = Map.merge(distances, reverse_distances(distances))

    get_max_distance(permutations, full_distances)
  end
end
puzzle_input
|> String.split("\n")
|> Enum.into(%{}, fn row ->
  Regex.named_captures(
    ~r/(?[a-zA-Z]+) to (?[a-zA-Z]+) = (?[0-9]+)/,
    row
  )
  |> then(fn %{"from" => from, "to" => to, "distance" => distance} ->
    {{from, to}, String.to_integer(distance)}
  end)
end)
|> TSP.solve()