Powered by AppSignal & Oban Pro

Day 11: Monkey in the Middle

Day 11: Monkey in the Middle.livemd

Day 11: Monkey in the Middle

Section

input =
  "Monkey 0:\n  Starting items: 79, 98\n  Operation: new = old * 19\n  Test: divisible by 23\n    If true: throw to monkey 2\n    If false: throw to monkey 3\n\nMonkey 1:\n  Starting items: 54, 65, 75, 74\n  Operation: new = old + 6\n  Test: divisible by 19\n    If true: throw to monkey 2\n    If false: throw to monkey 0\n\nMonkey 2:\n  Starting items: 79, 60, 97\n  Operation: new = old * old\n  Test: divisible by 13\n    If true: throw to monkey 1\n    If false: throw to monkey 3\n\nMonkey 3:\n  Starting items: 74\n  Operation: new = old + 3\n  Test: divisible by 17\n    If true: throw to monkey 0\n    If false: throw to monkey 1\n"

input =
  "Monkey 0:\n  Starting items: 74, 64, 74, 63, 53\n  Operation: new = old * 7\n  Test: divisible by 5\n    If true: throw to monkey 1\n    If false: throw to monkey 6\n\nMonkey 1:\n  Starting items: 69, 99, 95, 62\n  Operation: new = old * old\n  Test: divisible by 17\n    If true: throw to monkey 2\n    If false: throw to monkey 5\n\nMonkey 2:\n  Starting items: 59, 81\n  Operation: new = old + 8\n  Test: divisible by 7\n    If true: throw to monkey 4\n    If false: throw to monkey 3\n\nMonkey 3:\n  Starting items: 50, 67, 63, 57, 63, 83, 97\n  Operation: new = old + 4\n  Test: divisible by 13\n    If true: throw to monkey 0\n    If false: throw to monkey 7\n\nMonkey 4:\n  Starting items: 61, 94, 85, 52, 81, 90, 94, 70\n  Operation: new = old + 3\n  Test: divisible by 19\n    If true: throw to monkey 7\n    If false: throw to monkey 3\n\nMonkey 5:\n  Starting items: 69\n  Operation: new = old + 5\n  Test: divisible by 3\n    If true: throw to monkey 4\n    If false: throw to monkey 2\n\nMonkey 6:\n  Starting items: 54, 55, 58\n  Operation: new = old + 7\n  Test: divisible by 11\n    If true: throw to monkey 1\n    If false: throw to monkey 5\n\nMonkey 7:\n  Starting items: 79, 51, 83, 88, 93, 76\n  Operation: new = old * 3\n  Test: divisible by 2\n    If true: throw to monkey 0\n    If false: throw to monkey 6\n"
defmodule D11 do
  def round(monkeys, 0), do: monkeys

  def round(monkeys, times) do
    round(Enum.reduce(monkeys, monkeys, &turn/2), times - 1)
  end

  def turn(monkey, all) do
    get_in(all, [Access.filter(&(&1.id == monkey.id)), :items])
    |> hd()
    |> Enum.reduce(all, fn item, accu ->
      new_level =
        monkey.op.(item)
        |> rem(9_699_690)

      accu =
        accu
        |> update_in([Access.filter(&(&1.id == monkey.id)), :items], &tl/1)
        |> update_in([Access.filter(&(&1.id == monkey.id)), :kpi], &(&1 + 1))

      case new_level |> rem(monkey.div_by) do
        0 -> pass(new_level, monkey.true_target, accu)
        _ -> pass(new_level, monkey.false_target, accu)
      end
    end)
  end

  def pass(item, target, all) do
    update_in(all, [Access.filter(&(&1.id == target)), :items], &(&1 ++ [item]))
  end

  def monkey_parser(raw_monkey) do
    raw_monkey
    |> String.split("\n", trim: true)
    |> Enum.reduce(%{}, &line/2)
    |> Map.put(:kpi, 0)
  end

  def line("Monkey " <> id, map) do
    Map.put(map, :id, String.at(id, 0) |> String.to_integer())
  end

  def line("  Starting items: " <> items, map) do
    Map.put(map, :items, String.split(items, ", ") |> Enum.map(&amp;String.to_integer/1))
  end

  def line("  Operation: new = " <> op, map) do
    Map.put(map, :op, fn old -> Code.eval_string(op, old: old) |> elem(0) end)
  end

  def line("  Test: divisible by " <> div_by, map) do
    Map.put(map, :div_by, String.to_integer(div_by))
  end

  def line("    If true: throw to monkey " <> true_target, map),
    do: Map.put(map, :true_target, String.to_integer(true_target))

  def line("    If false: throw to monkey " <> false_target, map),
    do: Map.put(map, :false_target, String.to_integer(false_target))
end
import D11

monkeys =
  input
  |> String.split("\n\n", trim: true)
  |> Enum.map(&amp;monkey_parser/1)
  |> round(10_000)
  |> Enum.map(&amp; &amp;1.kpi)
  |> Enum.sort(:desc)
  |> Enum.take(2)
  |> then(&amp;apply(Kernel, :*, &amp;1))