Powered by AppSignal & Oban Pro

Day 11

2022/day11.livemd

Day 11

Mix.install([
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"}
])
:ok

Section

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2022", "11", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION"))
{:ok,
 "Monkey 0:\n  Starting items: 93, 98\n  Operation: new = old * 17\n  Test: divisible by 19\n    If true: throw to monkey 5\n    If false: throw to monkey 3\n\nMonkey 1:\n  Starting items: 95, 72, 98, 82, 86\n  Operation: new = old + 5\n  Test: divisible by 13\n    If true: throw to monkey 7\n    If false: throw to monkey 6\n\nMonkey 2:\n  Starting items: 85, 62, 82, 86, 70, 65, 83, 76\n  Operation: new = old + 8\n  Test: divisible by 5\n    If true: throw to monkey 3\n    If false: throw to monkey 0\n\nMonkey 3:\n  Starting items: 86, 70, 71, 56\n  Operation: new = old + 1\n  Test: divisible by 7\n    If true: throw to monkey 4\n    If false: throw to monkey 5\n\nMonkey 4:\n  Starting items: 77, 71, 86, 52, 81, 67\n  Operation: new = old + 4\n  Test: divisible by 17\n    If true: throw to monkey 1\n    If false: throw to monkey 6\n\nMonkey 5:\n  Starting items: 89, 87, 60, 78, 54, 77, 98\n  Operation: new = old * 7\n  Test: divisible by 2\n    If true: throw to monkey 1\n    If false: throw to monkey 4\n\nMonkey 6:\n  Starting items: 69, 65, 63\n  Operation: new = old + 6\n  Test: divisible by 3\n    If true: throw to monkey 7\n    If false: throw to monkey 2\n\nMonkey 7:\n  Starting items: 89\n  Operation: new = old * old\n  Test: divisible by 11\n    If true: throw to monkey 0\n    If false: throw to monkey 2\n"}
defmodule Monkey do
  defstruct items: [],
            operation: &Function.identity/1,
            test: nil,
            true: nil,
            false: nil,
            passes: 0

  def parse(input) do
    [
      _,
      "Starting items: " <> items,
      "Operation: new = old " <> operation,
      "Test: divisible by " <> test,
      "If true: throw to monkey " <> if_true,
      "If false: throw to monkey " <> if_false
    ] =
      input
      |> String.split("\n", trim: true)
      |> Enum.map(&amp;String.trim/1)

    %__MODULE__{
      items: parse_items(items),
      operation: parse_operation(operation),
      test: String.to_integer(test),
      true: String.to_integer(if_true),
      false: String.to_integer(if_false)
    }
  end

  def run(%__MODULE__{} = monkey, calming, lcm \\ 1) do
    {true_val, false_val} =
      monkey.items
      |> Enum.map(fn item ->
        item
        |> then(monkey.operation)
        |> div(calming)
        |> rem(lcm)
      end)
      |> Enum.split_with(fn item ->
        rem(item, monkey.test) == 0
      end)

    {struct(monkey, passes: monkey.passes + length(monkey.items), items: []),
     {monkey.true, true_val}, {monkey.false, false_val}}
  end

  def add_items(%__MODULE__{} = monkey, new_items) do
    struct(monkey, items: monkey.items ++ new_items)
  end

  defp parse_items(items) do
    items
    |> String.split(", ")
    |> Enum.map(&amp;String.to_integer/1)
  end

  defp parse_operation("* old"),
    do: fn old -> old * old end

  defp parse_operation("* " <> num) do
    num = String.to_integer(num)

    fn old -> old * num end
  end

  defp parse_operation("+ " <> num) do
    num = String.to_integer(num)

    fn old -> old + num end
  end
end
{:module, Monkey, <<70, 79, 82, 49, 0, 0, 24, ...>>, {:parse_operation, 1}}
monkeys =
  puzzle_input
  |> String.split("\n\n")
  |> Enum.map(&amp;Monkey.parse/1)
[
  %Monkey{
    items: ']b',
    operation: #Function<3.112681941/1 in Monkey.parse_operation/1>,
    test: 19,
    true: 5,
    false: 3,
    passes: 0
  },
  %Monkey{
    items: '_HbRV',
    operation: #Function<1.112681941/1 in Monkey.parse_operation/1>,
    test: 13,
    true: 7,
    false: 6,
    passes: 0
  },
  %Monkey{
    items: 'U>RVFASL',
    operation: #Function<1.112681941/1 in Monkey.parse_operation/1>,
    test: 5,
    true: 3,
    false: 0,
    passes: 0
  },
  %Monkey{
    items: 'VFG8',
    operation: #Function<1.112681941/1 in Monkey.parse_operation/1>,
    test: 7,
    true: 4,
    false: 5,
    passes: 0
  },
  %Monkey{
    items: 'MGV4QC',
    operation: #Function<1.112681941/1 in Monkey.parse_operation/1>,
    test: 17,
    true: 1,
    false: 6,
    passes: 0
  },
  %Monkey{
    items: 'YW,
    test: 2,
    true: 1,
    false: 4,
    passes: 0
  },
  %Monkey{
    items: 'EA?',
    operation: #Function<1.112681941/1 in Monkey.parse_operation/1>,
    test: 3,
    true: 7,
    false: 2,
    passes: 0
  },
  %Monkey{
    items: 'Y',
    operation: #Function<2.112681941/1 in Monkey.parse_operation/1>,
    test: 11,
    true: 0,
    false: 2,
    passes: 0
  }
]
defmodule MonkeyBusiness do
  import Kernel, except: [round: 1]

  defstruct monkeys: [], lcm: nil

  def new(monkeys) do
    lcm = Enum.reduce(monkeys, 1, &amp;lcm(&amp;1.test, &amp;2))

    %__MODULE__{
      monkeys: monkeys,
      lcm: lcm
    }
  end

  def run(%__MODULE__{} = mb, rounds, calming) do
    1..rounds
    |> Enum.reduce(mb, fn _, mb ->
      # IO.inspect(r, label: :round)
      round(mb, calming)
    end)
    |> Map.get(:monkeys)
    |> Enum.map(&amp; &amp;1.passes)
    |> Enum.sort(:desc)
    |> Enum.take(2)
    |> Enum.product()
  end

  defp round(%__MODULE__{} = mb, calming) do
    0..(length(mb.monkeys) - 1)
    |> Enum.reduce(mb, fn idx, mb ->
      {monkey, {a, a_val}, {b, b_val}} =
        mb.monkeys
        |> Enum.at(idx)
        |> Monkey.run(calming, mb.lcm)

      mb
      |> put_in([Access.key(:monkeys), Access.at(idx)], monkey)
      |> update_in([Access.key(:monkeys), Access.at(a)], &amp;Monkey.add_items(&amp;1, a_val))
      |> update_in([Access.key(:monkeys), Access.at(b)], &amp;Monkey.add_items(&amp;1, b_val))
    end)
  end

  defp lcm(a, b), do: div(a * b, Integer.gcd(a, b))
end
{:module, MonkeyBusiness, <<70, 79, 82, 49, 0, 0, 20, ...>>, {:lcm, 2}}

Task 1

monkeys
|> MonkeyBusiness.new()
|> MonkeyBusiness.run(20, 3)
78678

Task 2

monkeys
|> MonkeyBusiness.new()
|> MonkeyBusiness.run(10000, 1)
15333249714