Powered by AppSignal & Oban Pro

Day 11: Monkey in the Middle

Day 11: Monkey in the Middle.livemd

Day 11: Monkey in the Middle

Mix.install(nimble_parsec: ">= 1.2.3")

text = File.read!("Code/aoc.ex/input/2022/11.txt")

AoC

Parser

defmodule MonkeyMaster do
  defmodule Parser do
    import NimbleParsec

    monkey =
      ignore(repeat(ascii_char(not: ?0..?9)))
      |> unwrap_and_tag(integer(min: 1), :index)
      |> ignore(repeat(ascii_char(not: ?0..?9)))
      |> unwrap_and_tag(integer(min: 1), :start)
      |> repeat(ignore(string(", ")) |> unwrap_and_tag(integer(min: 1), :start))
      |> ignore(repeat(ascii_char(not: ?=..?=)))
      |> ignore(string("= "))
      |> unwrap_and_tag(ascii_string([(?\n + 1)..255], min: 1), :op)
      |> ignore(repeat(ascii_char(not: ?0..?9)))
      |> unwrap_and_tag(integer(min: 1), :divisor)
      |> ignore(repeat(ascii_char(not: ?0..?9)))
      |> unwrap_and_tag(integer(min: 1), :if_true)
      |> ignore(repeat(ascii_char(not: ?0..?9)))
      |> unwrap_and_tag(integer(min: 1), :if_false)

    input = wrap(monkey) |> repeat(ignore(string("\n\n")) |> wrap(monkey))

    defparsec(:input, input)
  end

  defp toss({idx, item, round}, data, adjuster) do
    monkey = elem(data, idx)
    new_item = item |> then(monkey[:op]) |> then(adjuster)
    new_idx = monkey[if(rem(new_item, monkey[:divisor]) == 0, do: :if_true, else: :if_false)]
    new_round = if new_idx < idx, do: round + 1, else: round
    {new_idx, new_item, new_round}
  end

  def run(data, mode, rounds) do
    data =
      data
      |> MonkeyMaster.Parser.input()
      |> elem(1)
      |> update_in(
        [Access.all(), Access.filter(&amp;match?({:op, _}, &amp;1)), Access.elem(1)],
        &amp;("fn old -> #{&amp;1} end" |> Code.eval_string() |> elem(0))
      )

    lcm = data |> Enum.map(&amp; &amp;1[:divisor]) |> Enum.reduce(&amp;Kernel.*/2)

    adjuster =
      case mode do
        :angrily -> &amp;rem(&amp;1, lcm)
        :nicely -> &amp;div(&amp;1, 3)
      end

    monkey_tuple = List.to_tuple(data)

    for monkey <- data,
        item <- Keyword.get_values(monkey, :start),
        stream = Stream.iterate({monkey[:index], item, 0}, &amp;toss(&amp;1, monkey_tuple, adjuster)),
        result <- Enum.take_while(stream, &amp;(elem(&amp;1, 2) < rounds)) do
      result
    end
    |> Enum.frequencies_by(&amp;elem(&amp;1, 0))
    |> Map.values()
    |> Enum.sort(:desc)
    |> Enum.take(2)
    |> Enum.reduce(&amp;Kernel.*/2)
  end
end
example = """
Monkey 0:
  Starting items: 79, 98
  Operation: new = old * 19
  Test: divisible by 23
    If true: throw to monkey 2
    If false: throw to monkey 3

Monkey 1:
  Starting items: 54, 65, 75, 74
  Operation: new = old + 6
  Test: divisible by 19
    If true: throw to monkey 2
    If false: throw to monkey 0

Monkey 2:
  Starting items: 79, 60, 97
  Operation: new = old * old
  Test: divisible by 13
    If true: throw to monkey 1
    If false: throw to monkey 3

Monkey 3:
  Starting items: 74
  Operation: new = old + 3
  Test: divisible by 17
    If true: throw to monkey 0
    If false: throw to monkey 1
"""

MonkeyMaster.run(example, :nicely, 20)
MonkeyMaster.run(example, :angrily, 10_000)
"Code/aoc.ex/input/2022/11.txt"
|> File.read!()
|> MonkeyMaster.run(:nicely, 20)
"Code/aoc.ex/input/2022/11.txt"
|> File.read!()
|> MonkeyMaster.run(:angrily, 10_000)