diff --git a/lib/y2024/README.md b/lib/y2024/README.md index 9fbfd99..85bf666 100644 --- a/lib/y2024/README.md +++ b/lib/y2024/README.md @@ -34,5 +34,6 @@ day 09, part 1 8.72 114.69 ms ±1.28% 114.98 ms 117. day 09, part 2 7.25 137.96 ms ±0.69% 137.79 ms 141.44 ms day 10, part 1 3.87 258.13 ms ±0.90% 258.51 ms 263.14 ms day 10, part 2 4.19 238.57 ms ±0.55% 238.51 ms 241.13 ms -day 11, part 1 16.03 62.37 ms ±3.10% 62.45 ms 66.15 ms +day 11, part 1 179.79 5.56 ms ±6.00% 5.62 ms 6.21 ms +day 11, part 2 13.81 72.41 ms ±2.44% 72.24 ms 76.25 ms ``` diff --git a/lib/y2024/day11.ex b/lib/y2024/day11.ex index d1e4709..fb8a3d0 100644 --- a/lib/y2024/day11.ex +++ b/lib/y2024/day11.ex @@ -2,81 +2,90 @@ defmodule Y2024.Day11 do use Advent.Day, no: 11 @doc """ - iex> Day11.part1([0, 1, 10, 99, 999], 1) - [1, 2024, 1, 0, 9, 9, 2021976] + iex> Day11.parts([0, 1, 10, 99, 999], 1) + 7 - iex> Day11.part1([125, 17], 1) - [253000, 1, 7] + iex> Day11.parts([125, 17], 1) + 3 - iex> Day11.part1([125, 17], 2) - [253, 0, 2024, 14168] + iex> Day11.parts([125, 17], 2) + 4 - iex> Day11.part1([125, 17], 3) - [512072, 1, 20, 24, 28676032] + iex> Day11.parts([125, 17], 3) + 5 - iex> Day11.part1([125, 17], 4) - [512, 72, 2024, 2, 0, 2, 4, 2867, 6032] + iex> Day11.parts([125, 17], 4) + 9 - iex> Day11.part1([125, 17], 5) - [1036288, 7, 2, 20, 24, 4048, 1, 4048, 8096, 28, 67, 60, 32] + iex> Day11.parts([125, 17], 5) + 13 - iex> Day11.part1([125, 17], 6) - [2097446912, 14168, 4048, 2, 0, 2, 4, 40, 48, 2024, 40, 48, 80, 96, 2, 8, 6, 7, 6, 0, 3, 2] + iex> Day11.parts([125, 17], 6) + 22 """ - def part1(input, times \\ 1) do + def parts(input, times \\ 1) do input - |> Enum.map(fn num -> %{number: num, digits: digits(num)} end) + |> Enum.reduce(%{}, fn num, acc -> + # We don't care about the order of stones, we only care about how many of each number we have + # So for each number, store what numbers we pre-compute to replace them with, and a count + Map.update(acc, num, {replace(num), 1}, fn {replace, count} -> {replace, count + 1} end) + end) |> blink(times) - |> Enum.map(& &1.number) + |> Enum.map(fn {_num, {_replace, count}} -> count end) + |> Enum.sum() end defp blink(input, 0), do: input defp blink(input, times) do input - |> Enum.reduce([], fn item, acc -> - {_, replace} = Enum.find(rules(), fn {match?, _} -> match?.(item) end) - [replace.(item) | acc] + |> Enum.reduce(%{}, fn {num, {to_replace, count}}, acc -> + # We might be replacing one stone with two, so reduce again + Enum.reduce(to_replace, acc, fn replace_num, acc -> + # If this is a number we've already seen, we know what to replace it with already + replace_with = Map.get(input, replace_num, {nil, 0}) |> elem(0) || replace(replace_num) + + Map.update(acc, replace_num, {replace_with, count}, fn {replace, old_count} -> + {replace, count + old_count} + end) + end) + |> Map.put_new(num, {to_replace, 0}) end) - |> Enum.reverse() - |> List.flatten() |> blink(times - 1) end - defp rules do + defp replace(stone) do + digits = digits(stone) + + replacement_rule = + Enum.find(replacement_rules(), fn {condition, _} -> condition.(stone, digits) end) + |> elem(1) + + replacement_rule.(stone, digits) + end + + defp replacement_rules do [ - {fn stone -> stone.number == 0 end, fn stone -> Map.put(stone, :number, 1) end}, - {fn stone -> rem(stone.digits, 2) == 0 end, - fn stone -> - left = div(stone.number, Integer.pow(10, div(stone.digits, 2))) - right = rem(stone.number, Integer.pow(10, div(stone.digits, 2))) + {fn stone, _digits -> stone == 0 end, fn _stone, _digits -> [1] end}, + {fn _stone, digits -> rem(digits, 2) == 0 end, + fn stone, digits -> + left = div(stone, Integer.pow(10, div(digits, 2))) + right = rem(stone, Integer.pow(10, div(digits, 2))) - [%{number: left, digits: digits(left)}, %{number: right, digits: digits(right)}] + [left, right] end}, - {fn _stone -> true end, - fn stone -> - new_number = stone.number * 2024 - %{stone | number: new_number, digits: digits(new_number)} - end} + {fn _stone, _digits -> true end, fn stone, _digits -> [stone * 2024] end} ] end defp digits(num), do: length(Integer.digits(num)) - # @doc """ - # iex> Day11.part2("update or delete me") - # "update or delete me" - # """ - # def part2(input) do - # input - # end - def parse_input(input) do input |> String.split(" ", trim: true) |> Enum.map(&String.to_integer/1) end - def part1_verify, do: input() |> parse_input() |> part1(25) |> length() - # def part2_verify, do: input() |> parse_input() |> part2() + def part1_verify, do: input() |> parse_input() |> parts(25) + def part2_verify, do: input() |> parse_input() |> parts(75) end