Powered by AppSignal & Oban Pro

AoC - Day11

2022/elixir/day11.livemd

AoC - Day11

Mix.install([
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"}
])

Setup

{:ok, data} = KinoAOC.download_puzzle("2022", "11", System.fetch_env!("LB_AOC_SECRET"))

Solve

test = """
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 Day11 do
  @keys ~w(items op div t f cnt)a

  def solve(data) do
    monkeys =
      data
      |> String.split("\n\n", trim: true)
      |> Enum.map(&format_part/1)
      |> Map.new()

    # 20 for T1
    1..20
    |> Enum.reduce(monkeys, fn _i, acc -> run(acc) end)
    |> Enum.map(fn {_, m} -> m.cnt end)
    |> Enum.sort(:desc)
    |> Enum.take(2)
    |> Enum.product()
  end

  def format_part(part), do: part |> String.split("\n", trim: true) |> row_to_monkey()

  def row_to_monkey([idx, items, op, tdiv, ct, cf]) do
    m_id = ext_id(idx)
    items = ext_items(items)
    op = ext_op(op)
    [tdiv, ct, cf] = Enum.map([tdiv, ct, cf], &ext_rest/1)
    # keep tuple? {items, op, tdiv, ct, cf, 0}
    v = [items, op, tdiv, ct, cf, 0]

    {m_id, @keys |> Enum.zip(v) |> Map.new()}
  end

  def ext_id(str),
    do: str |> String.split(" ") |> List.last() |> String.trim(":") |> String.to_integer()

  def ext_items(str) do
    str
    |> String.split(": ")
    |> List.last()
    |> String.split(",")
    |> Enum.map(fn num -> num |> String.trim() |> String.to_integer() end)
  end

  def ext_op(str), do: str |> String.split("new = ") |> List.last()
  def ext_rest(str), do: str |> String.split(" ") |> List.last() |> String.to_integer()

  def append(list, x), do: list |> Enum.reverse() |> then(&[x | &1]) |> Enum.reverse()

  def run(dd) do
    dd
    |> Enum.reduce(dd, fn {idx, _m}, acc ->
      # items op div t f cnt
      new_m = acc[idx]

      acc
      |> pass_items({new_m.items, new_m.op, new_m.div, new_m.t, new_m.f})
      |> update_in([idx, :cnt], fn x -> x + length(new_m.items) end)
      |> update_in([idx, :items], fn _ -> [] end)
    end)
  end

  def pass_items(acc, {items, op, div, t, f}) do
    Enum.reduce(items, acc, fn item, acc ->
      {new, _b} = Code.eval_string(op, old: item)
      # p1
      num = div(new, 3)
      idx_to = (rem(num, div) == 0 && t) || f

      update_in(acc, [idx_to, :items], &append(&1, num))
    end)
  end
end

# 10605
# 61503
Day11.solve(data)