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

Advent 2022 - Day 11

day11.livemd

Advent 2022 - Day 11

Mix.install([
  {:kino, github: "livebook-dev/kino"}
])
:ok

Setup

Part 1 was simple enough, but I did not come across the trick of part 2 without digging into the subreddit to figure it out… I cannot maths :)

input = Kino.Input.textarea("Please paste your input file:")
defmodule Parse do
  def remove_pre(str, pre), do: String.replace_prefix(str, pre, "")
  def remove_suf(str, suf), do: String.replace_suffix(str, suf, "")
  def to_int(str), do: String.to_integer(str)

  def monkey([index, items, op, test, if_true, if_false]) do
    index = index |> remove_pre("Monkey ") |> remove_suf(":") |> to_int

    items =
      items
      |> remove_pre("  Starting items: ")
      |> String.split(", ")
      |> Enum.map(&to_int/1)

    [l, op, r] =
      op
      |> remove_pre("  Operation: new = ")
      |> String.split(" ")

    l = if l == "old", do: :old, else: String.to_integer(l)
    op = String.to_atom(op)
    r = if r == "old", do: :old, else: String.to_integer(r)

    test =
      test
      |> remove_pre("  Test: divisible by ")
      |> to_int()

    if_true =
      if_true
      |> remove_pre("    If true: throw to monkey ")
      |> to_int()

    if_false =
      if_false
      |> remove_pre("    If false: throw to monkey ")
      |> to_int()

    %{
      index: index,
      items: items,
      op: {l, op, r},
      test: test,
      if_true: if_true,
      if_false: if_false,
      inspected: 0
    }
  end
end
{:module, Parse, <<70, 79, 82, 49, 0, 0, 13, ...>>, {:monkey, 1}}
monkeys =
  input
  |> Kino.Input.read()
  |> String.split("\n\n")
  |> Enum.map(&amp;String.split(&amp;1, "\n"))
  |> Enum.map(&amp;Parse.monkey/1)
[
  %{if_false: 3, if_true: 2, index: 0, inspected: 0, items: 'Ob', op: {:old, :*, 19}, test: 23},
  %{if_false: 0, if_true: 2, index: 1, inspected: 0, items: '6AKJ', op: {:old, :+, 6}, test: 19},
  %{if_false: 3, if_true: 1, index: 2, inspected: 0, items: 'O Enum.map(& &1[:test])
  |> Enum.reduce(fn a, b -> floor(QuickMaths.lcm(a, b)) end)
96577
defmodule Monkey do
  def next_worry(old, {l, op, r}, div) do
    l = if l == :old, do: old, else: l
    r = if r == :old, do: old, else: r
    Integer.floor_div(apply(Kernel, op, [l, r]), div)
  end

  def handle_inspect(monkey, [item | items], monkeys, lcm, div) do
    worry = next_worry(item, monkey[:op], div)

    is_divisible = Integer.mod(worry, monkey[:test]) == 0

    give_to_monkey =
      cond do
        is_divisible -> monkey[:if_true]
        true -> monkey[:if_false]
      end

    worry = rem(worry, lcm)

    monkeys =
      List.update_at(monkeys, give_to_monkey, fn monkey ->
        Map.update!(monkey, :items, &amp;(&amp;1 ++ [worry]))
      end)

    handle_inspect(monkey, items, monkeys, lcm, div)
  end

  def handle_inspect(_monkey, [], monkeys, _lcm, _div), do: monkeys

  def handle_monkey(index, monkeys, lcm, div) do
    monkey = Enum.at(monkeys, index)

    if monkey == nil do
      monkeys
    else
      monkeys = handle_inspect(monkey, monkey[:items], monkeys, lcm, div)

      monkeys =
        List.update_at(monkeys, index, fn monkey ->
          inspected = monkey[:inspected] + length(monkey[:items])
          %{monkey | items: [], inspected: inspected}
        end)

      handle_monkey(index + 1, monkeys, lcm, div)
    end
  end

  def handle_rounds(monkeys, _lcm, _div, rounds) when rounds == 0, do: monkeys

  def handle_rounds(monkeys, lcm, div, rounds) do
    monkeys = handle_monkey(0, monkeys, lcm, div)
    handle_rounds(monkeys, lcm, div, rounds - 1)
  end
end
{:module, Monkey, <<70, 79, 82, 49, 0, 0, 16, ...>>, {:handle_rounds, 4}}

Part 1

part1_monkeys = Monkey.handle_rounds(monkeys, lcm, 3, 20)

[first, second] =
  part1_monkeys
  |> Enum.map(&amp; &amp;1[:inspected])
  |> Enum.sort(:desc)
  |> Enum.take(2)

first * second
10605

Part 2

part2_monkeys = Monkey.handle_rounds(monkeys, lcm, 1, 10000)

[first, second] =
  part2_monkeys
  |> Enum.map(&amp; &amp;1[:inspected])
  |> Enum.sort(:desc)
  |> Enum.take(2)

first * second
2713310158