diff --git a/.tool-versions b/elixir/.tool-versions similarity index 100% rename from .tool-versions rename to elixir/.tool-versions diff --git a/elixir/lib/Y2022/day11.ex b/elixir/lib/Y2022/day11.ex new file mode 100644 index 0000000..10fe4d1 --- /dev/null +++ b/elixir/lib/Y2022/day11.ex @@ -0,0 +1,120 @@ +defmodule MonkeyUtil do + def parse_op(z, s) do + "Operation: new = " <> z, s -> parse_op(z, s) + "old " <> z, s -> parse_op(z, [:old | s]) + "old" <> z, s -> parse_op(z, [:old | s]) + "+ " <> z, s -> parse_op(z, [:plus | s]) + "- " <> z, s -> parse_op(z, [:minus | s]) + "* " <> z, s -> parse_op(z, [:mul | s]) + "/ " <> z, s -> parse_op(z, [:div | s]) + "", _, s -> s |> Enum.reverse() + n, s -> + [a | b] = String.split( n, " ") + a = String.trim(a) |> String.to_integer() + parse_op(b |> Enum.join(" "), [a | s]) + end + + def subs(b, a), do: if b == :old, do: a, else: b + def resolve_stack(x, y) do + (fn + old, [a, :plus, b] -> subs(a, old) + subs(b, old) + old, [a, :minus, b] -> subs(a, old) - subs(b, old) + old, [a, :mul, b] -> subs(a, old) * subs(b, old) + old, [a, :div, b] -> subs(a, old) / subs(b, old) end).(x, y) + end +end + +defmodule Monkey do + defstruct id: nil, items: nil, op: nil, mod: nil, if_true: nil, if_false: nil, inspections: 0 +end + +defmodule MonkeyMachine do + def load(instructions) do + {barrel, _} = for instruction <- instructions, reduce: {%{}, nil} do + {barrel, monkey} -> + (fn + {:break} -> + {barrel |> Map.put(monkey.id, monkey), nil} + {:add_monkey, id} -> {barrel, %Monkey{id: id}} + {:starting_items, i} -> {barrel, %{monkey | items: i }} + {a, o} -> {barrel, %{monkey | a => o }} + end).(instruction) + end + + barrel + end + + def run(barrel, :part1) do + run(barrel, 0, (Map.keys(barrel) |> Enum.count()) - 1) + end + + def run(barrel, :part2) do + run(barrel, 0, (Map.keys(barrel) |> Enum.count()) - 1, Map.values(barrel) |> Stream.map(&(&1.mod)) |> Enum.product()) + end + + defp run(barrel, i, n) when i > n, do: barrel + defp run(barrel, id, n) do + monkey = Map.get(barrel, id) + barrel = for item <- monkey.items, reduce: barrel do + b -> + item = div(monkey.op.(item), 3) + dest = if rem(item, monkey.mod) == 0, do: monkey.if_true, else: monkey.if_false + + b + |> Map.update!(dest, fn d -> %{d | items: [item | d.items]} end) + |> Map.update!(id, fn m -> Map.update!(m, :inspections, &(&1 + 1)) end) + end |> Map.update!(id, fn m -> %{m | items: []} end) + + run(barrel, id + 1, n) + end + + defp run(barrel, i, n, _) when i > n, do: barrel + defp run(barrel, id, n, m) do + monkey = Map.get(barrel, id) + barrel = for item <- monkey.items, reduce: barrel do + b -> + r = rem(monkey.op.(item), m) + item = if r == 0, do: m, else: r + dest = if rem(r, monkey.mod) == 0, do: monkey.if_true, else: monkey.if_false + + b + |> Map.update!(dest, fn d -> %{d | items: [item | d.items]} end) + |> Map.update!(id, fn m -> Map.update!(m, :inspections, &(&1 + 1)) end) + end |> Map.update!(id, fn m -> %{m | items: []} end) + + run(barrel, id + 1, n, m) + end +end + +defmodule MonkeySolver do + # part := :part1 || :part2 + def solve(input, part) do + barrel = input + |> String.split("\n") + |> Stream.map(&String.trim/1) + |> Enum.map( + fn + "Monkey " <> z -> {:add_monkey, z |> String.split(":") |> Enum.at(0) |> String.to_integer()} + "Starting items: " <> i -> {:starting_items, i |> String.split(",", trim: true) |> Enum.map(&String.to_integer(String.trim(&1)))} + "Operation: " <> _ = u -> {:op, fn o -> MonkeyUtil.resolve_stack(o, MonkeyUtil.parse_op(u, [])) end} + "Test: divisible by " <> n -> {:mod, String.to_integer(n)} + "If true: throw to monkey " <> n -> {:if_true, String.to_integer(n)} + "If false: throw to monkey " <> n -> {:if_false, String.to_integer(n)} + "" -> {:break} + u -> u + end + ) + |> MonkeyMachine.load() + + iterations = if part == :part1, do: 20, else: 10_000 end + + for _ <- 1..iterations, reduce: barrel do + b -> MonkeyMachine.run(b, part) + end + |> Map.values() + |> Stream.map(&(&1.inspections)) + |> Enum.sort(:desc) + |> Enum.take(2) + |> Enum.product() + end +end diff --git a/elixir/test/2022/day11_test.exs b/elixir/test/2022/day11_test.exs new file mode 100644 index 0000000..80470db --- /dev/null +++ b/elixir/test/2022/day11_test.exs @@ -0,0 +1,35 @@ +defmodule AoC.Y2022.Day11.Test do + use ExUnit.Case + + test "AoC.Y2022.Day11" do + assert MonkeySolver.solve(""" + 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 + """, :part1) == 10605 + end +end