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

Untitled notebook

2022/day/11/notebook.livemd

Untitled notebook

Mix.install([{:math, "~> 0.6.0"}])

Section

monkeys =
  File.read!("input.txt")
  # monkeys = File.read!("t1.txt")
  |> String.replace("\r", "")
  |> String.trim()
  |> String.split("\n\n")
  |> Enum.map(&String.split(&1, "\n"))
  |> Enum.map(fn [
                   <<"Monkey ", index::binary>>,
                   <<"  Starting items: ", starting::binary>>,
                   <<"  Operation: new = ", operation::binary>>,
                   <<"  Test: divisible by ", divisible_by::binary>>,
                   <<"    If true: throw to monkey ", if_true_to::binary>>,
                   <<"    If false: throw to monkey ", if_false_to::binary>>
                 ] ->
    {
      index |> String.trim_trailing(":") |> String.to_integer(),
      %{
        items:
          starting
          |> String.split(", ")
          |> Enum.map(&amp;String.to_integer/1),
        operation:
          operation
          |> String.split()
          |> then(fn
            ["old", op, "old"] -> {:old, op, :old}
            ["old", op, number] -> {:old, op, number |> String.to_integer()}
          end)
          |> then(fn
            {:old, "*", :old} -> fn old -> old * old end
            {:old, "+", :old} -> fn old -> old + old end
            {:old, "*", n} when is_integer(n) -> fn old -> old * n end
            {:old, "+", n} when is_integer(n) -> fn old -> old + n end
          end),
        divby: divisible_by |> String.to_integer(),
        true_target: if_true_to |> String.to_integer(),
        false_target: if_false_to |> String.to_integer(),
        inspections: 0
      }
    }
  end)
  |> Map.new()
0..(20 * map_size(monkeys) - 1)
|> Enum.reduce(monkeys, fn i, monks ->
  monki = rem(i, map_size(monkeys))

  {monk, monks} =
    monks
    |> Map.get_and_update(monki, fn monk ->
      {
        monk,
        %{monk | items: [], inspections: monk.inspections + length(monk.items)}
      }
    end)

  # IO.puts("Monkey #{monki}:")

  monk.items
  |> Enum.reduce(monks, fn item, monks ->
    new = div(monk.operation.(item), 3)
    target = if rem(new, monk.divby) == 0, do: monk.true_target, else: monk.false_target
    # IO.puts("  Item with worry level #{new} is thrown to monkey #{target}.")
    update_in(monks[target][:items], &amp;(&amp;1 ++ [new]))
  end)
end)
|> Map.values()
|> Enum.map(fn %{inspections: i} -> i end)
|> Enum.sort()
|> Enum.take(-2)
|> Enum.product()
rounds = 10000

lcm =
  Map.values(monkeys)
  |> Enum.map(&amp; &amp;1.divby)
  |> Enum.reduce(&amp;Math.lcm/2)

0..(rounds * map_size(monkeys) - 1)
|> Enum.reduce(monkeys, fn i, monks ->
  monki = rem(i, map_size(monkeys))

  {monk, monks} =
    monks
    |> Map.get_and_update(monki, fn monk ->
      {
        monk,
        %{monk | items: [], inspections: monk.inspections + length(monk.items)}
      }
    end)

  # IO.puts("Monkey #{monki}:")

  monk.items
  |> Enum.reduce(monks, fn item, monks ->
    new = rem(monk.operation.(item), lcm)
    target = if rem(new, monk.divby) == 0, do: monk.true_target, else: monk.false_target
    # IO.puts("  Item with worry level #{new} is thrown to monkey #{target}.")
    update_in(monks[target][:items], &amp;(&amp;1 ++ [new]))
  end)
end)
|> Map.values()
|> Enum.map(fn %{inspections: i} -> i end)
|> Enum.sort()
|> Enum.take(-2)
|> Enum.product()