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

Day 11: Monkey in the Middle

2022/day-11.livemd

Day 11: Monkey in the Middle

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

Day 11

sample_input = Kino.Input.textarea("Paste Sample Input")
real_input = Kino.Input.textarea("Paste Real Input")
defmodule MonkeyPack do
  def load(input, stressed? \\ false) do
    monkeys =
      input
      |> Kino.Input.read()
      |> String.split("\n\n")
      |> Enum.map(fn lines -> Monkey.load(lines) end)
      |> Enum.into(%{}, fn monkey -> {monkey.id, monkey} end)

    if stressed? do
      lcm = monkeys |> Map.values() |> Enum.map(& &1.divisor) |> Enum.product()

      monkeys
      |> Enum.map(fn {key, monkey} ->
        {key, %{monkey | finalize: fn worry -> rem(worry, lcm) end}}
      end)
      |> Enum.into(%{})
    else
      monkeys
    end
  end

  def process(monkeys, rounds) do
    0..(rounds - 1)
    |> Enum.reduce(monkeys, fn _count, monkeys -> MonkeyPack.process_round(monkeys) end)
  end

  def process_round(monkeys) do
    0..(Enum.count(monkeys) - 1)
    |> Enum.reduce(monkeys, fn index, monkeys ->
      monkey = monkeys["#{index}"]
      {items, updated_monkey} = Monkey.inspect_items(monkey)
      distribute_items(Map.put(monkeys, monkey.id, updated_monkey), items)
    end)
  end

  def distribute_items(monkeys, items) do
    Enum.reduce(items, monkeys, fn {monkey_id, worry}, monkeys ->
      Map.put(monkeys, monkey_id, Monkey.receive_item(monkeys[monkey_id], worry))
    end)
  end
end
defmodule Monkey do
  defstruct id: "0",
            items: [],
            total_processed: 0,
            operation: nil,
            divisor: 1,
            true_recipient: "1",
            false_recipient: "1",
            finalize: nil

  def load(details) do
    details
    |> String.split("\n")
    |> Enum.reduce(
      %__MODULE__{finalize: fn worry -> div(worry, 3) end},
      fn line, monkey -> process_details(monkey, line) end
    )
  end

  def inspect_items(monkey) do
    items =
      Enum.map(monkey.items, fn initial_worry ->
        final_worry = initial_worry |> monkey.operation.() |> monkey.finalize.()

        recipient =
          if Monkey.test(monkey, final_worry),
            do: monkey.true_recipient,
            else: monkey.false_recipient

        {recipient, final_worry}
      end)

    {items, %{monkey | items: [], total_processed: monkey.total_processed + length(items)}}
  end

  def test(%{divisor: divisor}, worry), do: rem(worry, divisor) == 0

  def receive_item(monkey, worry), do: %{monkey | items: monkey.items ++ [worry]}

  def process_details(monkey, "Monkey " <> <> <> ":") do
    %{monkey | id: id}
  end

  def process_details(monkey, "  Starting items: " <> raw_items) do
    items =
      raw_items
      |> String.split(",")
      |> Enum.map(fn item -> item |> String.trim() |> String.to_integer() end)

    %{monkey | items: items}
  end

  def process_details(monkey, "  Operation: new = old * old") do
    %{monkey | operation: fn worry -> worry * worry end}
  end

  def process_details(
        monkey,
        "  Operation: new = old " <> <> <> " " <> raw_amount
      ) do
    amount = String.to_integer(raw_amount)

    operation =
      case op do
        "+" -> fn worry -> worry + amount end
        "*" -> fn worry -> worry * amount end
      end

    %{monkey | operation: operation}
  end

  def process_details(monkey, "  Test: divisible by " <> raw_divisor) do
    divisor = String.to_integer(raw_divisor)
    %{monkey | divisor: divisor}
  end

  def process_details(monkey, "    If true: throw to monkey " <> recipient) do
    %{monkey | true_recipient: recipient}
  end

  def process_details(monkey, "    If false: throw to monkey " <> recipient) do
    %{monkey | false_recipient: recipient}
  end
end
sample_input |> MonkeyPack.load() |> MonkeyPack.process(20)
real_input |> MonkeyPack.load() |> MonkeyPack.process(20)
266 * 274
real_input |> MonkeyPack.load(true) |> MonkeyPack.process(10_000)
123_733 * 123_741