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

Day 11

day11.livemd

Day 11

Input

input = """
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
"""
input = """
Monkey 0:
  Starting items: 85, 77, 77
  Operation: new = old * 7
  Test: divisible by 19
    If true: throw to monkey 6
    If false: throw to monkey 7

Monkey 1:
  Starting items: 80, 99
  Operation: new = old * 11
  Test: divisible by 3
    If true: throw to monkey 3
    If false: throw to monkey 5

Monkey 2:
  Starting items: 74, 60, 74, 63, 86, 92, 80
  Operation: new = old + 8
  Test: divisible by 13
    If true: throw to monkey 0
    If false: throw to monkey 6

Monkey 3:
  Starting items: 71, 58, 93, 65, 80, 68, 54, 71
  Operation: new = old + 7
  Test: divisible by 7
    If true: throw to monkey 2
    If false: throw to monkey 4

Monkey 4:
  Starting items: 97, 56, 79, 65, 58
  Operation: new = old + 5
  Test: divisible by 5
    If true: throw to monkey 2
    If false: throw to monkey 0

Monkey 5:
  Starting items: 77
  Operation: new = old + 4
  Test: divisible by 11
    If true: throw to monkey 4
    If false: throw to monkey 3

Monkey 6:
  Starting items: 99, 90, 84, 50
  Operation: new = old * old
  Test: divisible by 17
    If true: throw to monkey 7
    If false: throw to monkey 1

Monkey 7:
  Starting items: 50, 66, 61, 92, 64, 78
  Operation: new = old + 3
  Test: divisible by 2
    If true: throw to monkey 5
    If false: throw to monkey 1
"""

Monkey data structure

defmodule Monkey do
  defstruct [:operation, :test, :monkey_true, :monkey_false, items: [], inspected: 0]

  defp append_items_to(items, monkey) do
    %{monkey | items: monkey.items ++ items}
  end

  def parse_monkey(input) do
    input
    |> String.split("\n")
    |> Enum.reduce(%Monkey{}, fn line, monkey ->
      case line do
        "  Starting items: " <> items ->
          items
          |> String.split(", ")
          |> Enum.map(&amp;String.to_integer/1)
          |> append_items_to(monkey)

        "  Operation: new = old " <> str ->
          [op, n] = str |> String.split()

          n = fn old ->
            case n do
              "old" -> old
              _ -> String.to_integer(n)
            end
          end

          f =
            case op do
              "*" -> &amp;(n.(&amp;1) * &amp;1)
              "+" -> &amp;(n.(&amp;1) + &amp;1)
            end

          %{monkey | operation: f}

        "  Test: divisible by " <> n ->
          %{monkey | test: String.to_integer(n)}

        "    If true: throw to monkey " <> n ->
          %{monkey | monkey_true: String.to_integer(n)}

        "    If false: throw to monkey " <> n ->
          %{monkey | monkey_false: String.to_integer(n)}

        _ ->
          monkey
      end
    end)
  end

  def round(monkeys) do
    for i <- 0..(Map.size(monkeys) - 1), reduce: monkeys do
      monkeys ->
        {throw_true, throw_false} =
          monkeys[i].items
          |> Enum.map(fn n -> div(monkeys[i].operation.(n), 3) end)
          |> Enum.split_with(fn n -> rem(n, monkeys[i].test) == 0 end)

        {monkey_true, monkey_false} = {monkeys[i].monkey_true, monkeys[i].monkey_false}
        item_key = Access.key(:items)

        monkeys
        |> put_in([i, item_key], [])
        |> update_in([i, Access.key(:inspected)], &amp;(&amp;1 + length(monkeys[i].items)))
        |> update_in([monkey_true, item_key], &amp;(&amp;1 ++ throw_true))
        |> update_in([monkey_false, item_key], &amp;(&amp;1 ++ throw_false))
    end
  end
end

Part 1

monkeys =
  input
  |> String.split("\n\n")
  |> Enum.map(&amp;Monkey.parse_monkey/1)
  |> Enum.with_index(fn k, v -> {v, k} end)
  |> Map.new()

# |> IO.inspect(charlists: :as_lists)

Enum.reduce(1..20, monkeys, fn _, monkeys -> Monkey.round(monkeys) end)
|> Map.values()
|> Enum.map(&amp; &amp;1.inspected)
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.product()

Part 2

defmodule Monkey2 do
  defstruct [:operation, :test, :monkey_true, :monkey_false, items: [], inspected: 0]

  def parse_monkey(input) do
    input
    |> String.split("\n")
    |> Enum.reduce(%Monkey2{}, fn line, monkey ->
      case line do
        "  Starting items: " <> items ->
          items
          |> String.split(", ")
          |> Enum.map(&amp;String.to_integer/1)
          |> then(&amp;%{monkey | items: [&amp;1]})

        "  Operation: new = old " <> str ->
          [op, n] = str |> String.split()

          n = fn old ->
            case n do
              "old" -> old
              _ -> String.to_integer(n)
            end
          end

          f =
            case op do
              "*" -> &amp;(n.(&amp;1) * &amp;1)
              "+" -> &amp;(n.(&amp;1) + &amp;1)
            end

          %{monkey | operation: f}

        "  Test: divisible by " <> n ->
          %{monkey | test: String.to_integer(n)}

        "    If true: throw to monkey " <> n ->
          %{monkey | monkey_true: String.to_integer(n)}

        "    If false: throw to monkey " <> n ->
          %{monkey | monkey_false: String.to_integer(n)}

        _ ->
          monkey
      end
    end)
  end

  def round(monkeys) do
    all_tests =
      monkeys
      |> Map.values()
      |> Enum.map(&amp; &amp;1.test)
      |> Enum.product()

    for i <- 0..(Map.size(monkeys) - 1), reduce: monkeys do
      monkeys ->
        items = Enum.concat(monkeys[i].items)
        # IO.inspect(monkeys[i], label: i, charlists: :as_lists)
        # IO.inspect(items, charlists: :as_lists)
        {throw_true, throw_false} =
          items
          |> Enum.map(fn n -> rem(monkeys[i].operation.(n), all_tests) end)
          |> Enum.split_with(fn n -> rem(n, monkeys[i].test) == 0 end)

        # IO.inspect({throw_true, throw_false}, charlists: :as_lists)

        {monkey_true, monkey_false} = {monkeys[i].monkey_true, monkeys[i].monkey_false}
        item_key = Access.key(:items)

        monkeys
        |> put_in([i, item_key], [])
        |> update_in([i, Access.key(:inspected)], &amp;(&amp;1 + length(items)))
        |> update_in([monkey_true, item_key], &amp;[throw_true | &amp;1])
        |> update_in([monkey_false, item_key], &amp;[throw_false | &amp;1])
    end
  end
end
monkeys =
  input
  |> String.split("\n\n")
  |> Enum.map(&amp;Monkey2.parse_monkey/1)
  |> Enum.with_index(fn k, v -> {v, k} end)
  |> Map.new()

# |> IO.inspect(charlists: :as_lists)

Enum.reduce(1..10000, monkeys, fn _, monkeys -> Monkey2.round(monkeys) end)
# |> IO.inspect(charlists: :as_lists)
|> Map.values()
|> Enum.map(&amp; &amp;1.inspected)
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.product()