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

Day 11

livebooks/2022/11.livemd

Day 11

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

alias VegaLite, as: Vl

day = 11

aoc_session = System.fetch_env!("LB_AOC_SESSION")
input_url = "https://adventofcode.com/2022/day/#{day}/input"
{:ok, %{body: input}} = Req.get(input_url, headers: [cookie: "session=#{aoc_session}"])

Input

test_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
"""
defmodule Monkey do
  defstruct id: 0,
            items: [],
            op: nil,
            mod: 1,
            test: nil,
            true_target: 0,
            false_target: 0,
            inspections: 0

  def inspect(monkey, worry_level, gcd, worry_divisor \\ 3) do
    worry_level = (monkey.op.(worry_level) / worry_divisor) |> floor()
    worry_level = rem(worry_level, gcd)
    target = if monkey.test.(worry_level), do: monkey.true_target, else: monkey.false_target

    m =
      monkey
      |> Map.put(:items, Enum.drop(monkey.items, 1))
      |> Map.put(:inspections, monkey.inspections + 1)

    {m, worry_level, target}
  end
end
defmodule Input do
  def load(input) do
    for monkey_str <- String.split(input, "\n\n", trim: true), into: %{} do
      monkey = parse_monkey_str(monkey_str)
      {monkey.id, monkey}
    end
  end

  def parse_monkey_str(monkey_str) do
    monkey_str
    |> String.split("\n", trim: true)
    |> Enum.map(&amp;String.trim/1)
    |> Enum.reduce(%Monkey{}, fn
      "Monkey " <> n, monkey ->
        %{monkey | id: parse_id(n)}

      "Starting items: " <> items, monkey ->
        %{monkey | items: parse_items(items)}

      "Operation: new = old * old", monkey ->
        %{monkey | op: fn x -> x * x end}

      "Operation: new = old " <> op, monkey ->
        %{monkey | op: parse_op(op)}

      "Test: divisible by " <> n, monkey ->
        monkey |> Map.put(:test, parse_test(n)) |> Map.put(:mod, String.to_integer(n))

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

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

  def parse_id(str), do: str |> String.at(0) |> String.to_integer()

  def parse_test(modulo) do
    fn x -> rem(x, String.to_integer(modulo)) == 0 end
  end

  def parse_items(str) do
    str
    |> String.split(", ", trim: true)
    |> Enum.map(&amp;String.to_integer/1)
  end

  def parse_op("* " <> n), do: fn old -> old * String.to_integer(n) end
  def parse_op("+ " <> n), do: fn old -> old + String.to_integer(n) end
end

Part 1

defmodule MonkeyBusiness do
  def calc(input, num_rounds, worry_divisor) do
    monkeys = Input.load(input)
    gcd = get_gcd(monkeys)

    1..num_rounds
    |> Enum.reduce(monkeys, fn _, monkeys -> do_round(monkeys, gcd, worry_divisor) end)
    |> Enum.map(fn {_, m} -> m.inspections end)
    |> Enum.sort()
    |> Enum.reverse()
    |> Enum.take(2)
    |> Enum.product()
  end

  def do_round(monkeys, gcd, worry_divisor) do
    ids = Map.keys(monkeys) |> Enum.sort()

    Enum.reduce(ids, monkeys, fn i, monkeys ->
      m = monkeys[i]

      Enum.reduce(m.items, monkeys, fn item, monkeys ->
        m = monkeys[i]
        {m, worry_level, id} = Monkey.inspect(m, item, gcd, worry_divisor)
        monkeys = Map.put(monkeys, m.id, %{m | items: Enum.drop(m.items, 1)})

        new_items = monkeys[id].items ++ [worry_level]
        new_monkey = %{monkeys[id] | items: new_items}

        Map.put(monkeys, id, new_monkey)
      end)
    end)
  end

  def get_gcd(monkeys) do
    monkeys
    |> Enum.map(fn {_id, m} -> m.mod end)
    |> Enum.product()
  end
end
MonkeyBusiness.calc(input, 20, 3)

Part 2

MonkeyBusiness.calc(input, 10_000, 1)