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

Day 11

2022/day11.livemd

Day 11

Mix.install([{:kino, "~>0.7.0"}])

Setup

input = Kino.Input.textarea("input file")

Common

defmodule Day11 do
  def get_monkey_structure(data) do
    data
    |> Enum.map(fn monkey ->
      monkey
      |> String.split("\n", trim: true)
      |> Enum.map(&String.trim/1)
      |> Enum.reduce(
        %{
          monkey_id: -1,
          items: [],
          operation: "",
          divisible_by: 1,
          if_false_throw_to: -1,
          if_true_throw_to: -1,
          times_inspected_items: 0
        },
        fn data, acc -> parse_monkey_data(acc, data) end
      )
    end)
    |> Enum.sort(fn first, second ->
      first.monkey_id < second.monkey_id
    end)
    |> Stream.with_index(0)
    |> Enum.reduce(%{}, fn {v, k}, acc -> Map.put(acc, k, v) end)
  end

  def play_round(monkeys, worry_factor \\ 3) do
    monkeys
    |> Enum.reduce(monkeys, fn {id, monkey_data}, acc ->
      items_to_move =
        acc[id].items
        |> Enum.map(fn item ->
          item
          |> String.to_integer()
          |> new_worry_level(acc[id].operation)
          |> then(fn val ->
            case worry_factor == 3 do
              true ->
                floor(val / 3)

              false ->
                rem(val, worry_factor)
            end
          end)
          |> then(fn val ->
            case rem(val, acc[id].divisible_by) do
              0 ->
                {acc[id].if_true_throw_to, val}

              _ ->
                {acc[id].if_false_throw_to, val}
            end
          end)
        end)

      new_monkey_data = %{
        monkey_data
        | items: [],
          times_inspected_items: monkey_data.times_inspected_items + length(items_to_move)
      }

      updated_monkeys =
        items_to_move
        |> Enum.reduce(acc, fn {to, item}, acc ->
          new_monkey = %{acc[to] | items: acc[to].items ++ [Integer.to_string(item)]}

          acc
          |> Map.put(to, new_monkey)
        end)

      updated_monkeys
      |> Map.put(id, new_monkey_data)
    end)
  end

  # Private methods
  defp new_worry_level(current_worry_level, "new = old * old") do
    current_worry_level * current_worry_level
  end

  defp new_worry_level(current_worry_level, "new = old * " <> number) do
    current_worry_level * String.to_integer(number)
  end

  defp new_worry_level(current_worry_level, "new = old + " <> number) do
    current_worry_level + String.to_integer(number)
  end

  defp parse_monkey_data(monkey_data, "Monkey " <> monkey_id) do
    %{monkey_data | monkey_id: monkey_id |> String.replace(":", "") |> String.to_integer()}
  end

  defp parse_monkey_data(monkey_data, "Starting items: " <> items) do
    %{monkey_data | items: items |> String.split(",", trim: true) |> Enum.map(&amp;String.trim/1)}
  end

  defp parse_monkey_data(monkey_data, "Operation: " <> operation) do
    %{monkey_data | operation: operation}
  end

  defp parse_monkey_data(monkey_data, "Test: divisible by " <> divisible_by) do
    %{monkey_data | divisible_by: String.to_integer(divisible_by)}
  end

  defp parse_monkey_data(monkey_data, "If false: throw to monkey " <> if_false_throw_to) do
    %{monkey_data | if_false_throw_to: String.to_integer(if_false_throw_to)}
  end

  defp parse_monkey_data(monkey_data, "If true: throw to monkey " <> if_true_throw_to) do
    %{monkey_data | if_true_throw_to: String.to_integer(if_true_throw_to)}
  end
end

Part 1

monkeys =
  input
  |> Kino.Input.read()
  |> String.split("\n\n", trim: true)
  |> Day11.get_monkey_structure()

1..20
|> Enum.reduce(monkeys, fn _, acc ->
  acc
  |> Day11.play_round()
end)
|> Enum.map(fn {_, item} -> item.times_inspected_items end)
|> Enum.sort(&amp;(&amp;1 >= &amp;2))
|> Enum.take(2)
|> then(fn [first, second] ->
  first * second
end)

Part 2

monkeys =
  input
  |> Kino.Input.read()
  |> String.split("\n\n", trim: true)
  |> Day11.get_monkey_structure()

divisible_by =
  monkeys
  |> Enum.map(fn {_, monkey} -> monkey.divisible_by end)
  |> Enum.reduce(1, fn val, acc -> val * acc end)

1..10_000
|> Enum.reduce(monkeys, fn _round, acc ->
  acc
  |> Day11.play_round(divisible_by)
end)
|> Enum.map(fn {_, item} -> item.times_inspected_items end)
|> Enum.sort(&amp;(&amp;1 >= &amp;2))
|> Enum.take(2)
|> then(fn [first, second] ->
  first * second
end)