Powered by AppSignal & Oban Pro

Advent of Code 2022

2022/day11.livemd

Advent of Code 2022

Mix.install([
  {:req, "~> 0.3.2"}
])

Day 11

input =
  "https://adventofcode.com/2022/day/11/input"
  |> Req.get!(headers: [cookie: "session=#{System.get_env("AOC_COOKIE")}"])
  |> Map.get(:body)
sample = """
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 = sample
monkeys =
  input
  |> String.split("\n\n", trim: true)
  |> Enum.map(fn monkey_lines ->
    monkey_lines
    |> String.split("\n", trim: true)
    |> Enum.map(&String.trim/1)
    |> Enum.reduce(%{inspect_count: 0}, fn line, acc ->
      case line do
        "Monkey" <> _ ->
          n = Regex.run(~r/Monkey (\d+):/, line, capture: :all_but_first) |> hd()
          Map.put(acc, :id, String.to_integer(n))

        "Starting items: " <> rest ->
          items =
            rest
            |> String.split(",", trim: true)
            |> Enum.map(&amp;String.trim/1)
            |> Enum.map(&amp;String.to_integer/1)

          Map.put(acc, :items, items)

        "Operation: new = " <> operation ->
          operation =
            operation
            |> String.split(" ")
            |> then(fn [a, op, b] ->
              a = if a == "old", do: :old, else: String.to_integer(a)
              b = if b == "old", do: :old, else: String.to_integer(b)

              {a, op, b}
            end)

          Map.put(acc, :operation, operation)

        "Test: divisible by " <> divisor ->
          divisor = String.to_integer(divisor)
          Map.put(acc, :test_divisor, divisor)

        "If true: throw to monkey " <> n ->
          to_monkey = String.to_integer(n)
          Map.put(acc, true, to_monkey)

        "If false: throw to monkey " <> n ->
          to_monkey = String.to_integer(n)
          Map.put(acc, false, to_monkey)

        _ ->
          raise "unknown line #{line}"
      end
    end)
    |> then(fn monkey ->
      {Map.get(monkey, :id), monkey}
    end)
  end)
  |> Enum.into(%{})
n = Enum.count(monkeys)

Part 1

1..20
|> Enum.reduce(monkeys, fn _i, monkeys ->
  0..(n - 1)
  |> Enum.reduce(monkeys, fn i, monkeys ->
    get_in(monkeys, [i, :items])
    |> Enum.reduce(monkeys, fn item, monkeys ->
      worry =
        case get_in(monkeys, [i, :operation]) do
          {a, "*", b} ->
            a = if a == :old, do: item, else: a
            b = if b == :old, do: item, else: b
            a * b

          {a, "+", b} ->
            a = if a == :old, do: item, else: a
            b = if b == :old, do: item, else: b
            a + b
        end
        |> div(3)

      divisible? = if rem(worry, get_in(monkeys, [i, :test_divisor])) == 0, do: true, else: false
      to_monkey = get_in(monkeys, [i, divisible?])

      IO.puts("#{i} (#{item}) -> #{to_monkey} (#{worry})")

      update_in(monkeys, [i, :items], &amp;tl(&amp;1))
      |> update_in([i, :inspect_count], &amp;(&amp;1 + 1))
      |> update_in([to_monkey, :items], &amp;(&amp;1 ++ [worry]))
    end)
  end)
end)
|> Enum.map(fn {_, monkey} -> monkey.inspect_count end)
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.reduce(fn a, b -> a * b end)

Part 2

# All the divisors are prime numbers.
# We can divide the worry level by the product (ie. greatest common divisor).
# If we don't divide, the worry level will become super huge!
# The BEAM can handle it but it will take forever and run out of memory.
product = Enum.reduce(monkeys, 1, fn {_, monkey}, p -> p * monkey.test_divisor end)

1..10000
|> Enum.reduce(monkeys, fn _i, monkeys ->
  0..(n - 1)
  |> Enum.reduce(monkeys, fn i, monkeys ->
    get_in(monkeys, [i, :items])
    |> Enum.reduce(monkeys, fn item, monkeys ->
      worry =
        case get_in(monkeys, [i, :operation]) do
          {a, "*", b} ->
            a = if a == :old, do: item, else: a
            b = if b == :old, do: item, else: b
            a * b

          {a, "+", b} ->
            a = if a == :old, do: item, else: a
            b = if b == :old, do: item, else: b
            a + b
        end
        |> rem(product)

      divisible? = if rem(worry, get_in(monkeys, [i, :test_divisor])) == 0, do: true, else: false
      to_monkey = get_in(monkeys, [i, divisible?])

      update_in(monkeys, [i, :items], &amp;tl(&amp;1))
      |> update_in([i, :inspect_count], &amp;(&amp;1 + 1))
      |> update_in([to_monkey, :items], &amp;(&amp;1 ++ [worry]))
    end)
  end)
end)
|> Enum.map(fn {_, monkey} -> monkey.inspect_count end)
|> Enum.sort(:desc)
|> Enum.take(2)
|> Enum.reduce(fn a, b -> a * b end)