Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 13: Knights of the Dinner Table

2015/13.livemd

Day 13: Knights of the Dinner Table

Mix.install([
  {:kino, "~> 0.12.3"}
])

Modules

defmodule Parser do
  def parse(input) do
    input
    |> String.split("\n")
    |> Enum.map(&parse_line/1)
    |> Enum.reduce(%{}, fn
      {subject, neighbour, amount}, acc when not is_map_key(acc, subject) ->
        Map.put(acc, subject, Map.new([{neighbour, amount}]))

      {subject, neighbour, amount}, acc ->
        Map.put(acc, subject, Map.put(acc[subject], neighbour, amount))
    end)
  end

  defp parse_line(input) do
    {subject, neighbour} = parse_names(input)
    modifier = parse_modifier(input)
    amount = parse_amount(input)
    {subject, neighbour, modifier.(amount)}
  end

  defp parse_names(input) do
    Regex.scan(~r/[A-Z]\w+/, input)
    |> then(fn [[subject], [neighbour]] -> [subject, neighbour] end)
    |> Enum.map(&String.downcase/1)
    |> Enum.map(&String.to_atom/1)
    |> List.to_tuple()
  end

  defp parse_modifier(input) do
    Regex.run(~r/gain|lose/, input)
    |> then(fn
      ["gain"] -> fn x -> x end
      ["lose"] -> fn x -> -x end
    end)
  end

  defp parse_amount(input) do
    Regex.run(~r/\d+/, input)
    |> then(fn [amount] -> amount end)
    |> String.to_integer()
  end
end
defmodule DinnerTable do
  defstruct [:rules]

  def init(rules), do: %DinnerTable{rules: rules}

  def optimal(table, extras \\ []) do
    permutations(Map.keys(table.rules) ++ extras)
    |> Enum.map(&happiness(table, &1))
    |> Enum.max()
  end

  defp happiness(table, perm) do
    (perm ++ [List.first(perm)] ++ Enum.reverse(perm))
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.map(fn [subject, neighbour] ->
      Map.get(table.rules, subject, %{}) |> Map.get(neighbour, 0)
    end)
    |> Enum.sum()
  end

  # base case for permutations
  defp permutations([]), do: [[]]

  # returns a list of all the permutations of elements within 
  # the provided list
  defp permutations(list) do
    for elem <- list, rest <- permutations(list -- [elem]), do: [elem | rest]
  end
end

Input

input = Kino.Input.textarea("Please paste your puzzle input:")

Part 1

input
|> Kino.Input.read()
|> Parser.parse()
|> DinnerTable.init()
|> DinnerTable.optimal()

Part 2

input
|> Kino.Input.read()
|> Parser.parse()
|> DinnerTable.init()
|> DinnerTable.optimal([:dean])