Skip to content

Commit

Permalink
2024 day 11, part 2
Browse files Browse the repository at this point in the history
The lanternfish rule!
  • Loading branch information
sevenseacat committed Dec 11, 2024
1 parent df35f1a commit 9b9c1f6
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 45 deletions.
3 changes: 2 additions & 1 deletion lib/y2024/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
97 changes: 53 additions & 44 deletions lib/y2024/day11.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 9b9c1f6

Please sign in to comment.