155 lines
4.4 KiB
Elixir
155 lines
4.4 KiB
Elixir
defmodule Day14 do
|
|
# Cyclical linked list
|
|
# Use a map, with a ref as the key.
|
|
# Each element contains the value, and next pointer
|
|
# Keep two pointers for the current recipies
|
|
defmodule Recipe do
|
|
defstruct ~w/score next/a
|
|
end
|
|
|
|
defmodule State do
|
|
defstruct ~w/recipes first_elf second_elf last target count target_count last_seq desired_sequence/a
|
|
end
|
|
|
|
def init(target_count) do
|
|
desired_sequence =
|
|
target_count
|
|
|> Integer.to_string()
|
|
|> String.split("", trim: true)
|
|
|> Enum.map(&String.to_integer/1)
|
|
|> List.to_tuple()
|
|
|
|
last_seq =
|
|
(for(_i <- 1..(tuple_size(desired_sequence) - 2), do: nil) ++ [3, 7])
|
|
|> List.to_tuple()
|
|
|
|
first_elf = System.unique_integer()
|
|
second_elf = System.unique_integer()
|
|
|
|
%State{
|
|
first_elf: first_elf,
|
|
second_elf: second_elf,
|
|
last: second_elf,
|
|
target: nil,
|
|
target_count: target_count,
|
|
count: 2,
|
|
desired_sequence: desired_sequence,
|
|
last_seq: last_seq,
|
|
recipes: %{
|
|
first_elf => %Recipe{score: 3, next: second_elf},
|
|
second_elf => %Recipe{score: 7, next: first_elf}
|
|
}
|
|
}
|
|
end
|
|
|
|
def combine_recipes(a, b) do
|
|
(a + b)
|
|
|> Integer.to_string()
|
|
|> String.split("", trim: true)
|
|
|> Enum.map(&String.to_integer/1)
|
|
end
|
|
|
|
def append_score(score, %{last: prev_last, recipes: recipes} = state) do
|
|
last_seq =
|
|
state.last_seq
|
|
|> Tuple.delete_at(0)
|
|
|> Tuple.append(score)
|
|
|
|
last = System.unique_integer()
|
|
|
|
updated_recipes =
|
|
recipes
|
|
|> Map.put(prev_last, %Recipe{recipes[prev_last] | next: last})
|
|
|> Map.put(last, %Recipe{score: score, next: recipes[prev_last].next})
|
|
|
|
state = %State{
|
|
state
|
|
| last: last,
|
|
recipes: updated_recipes,
|
|
count: state.count + 1
|
|
}
|
|
|
|
state =
|
|
if state.count === state.target_count do
|
|
%State{state | target: last}
|
|
else
|
|
state
|
|
end
|
|
|
|
if state.last_seq === state.desired_sequence do
|
|
state
|
|
else
|
|
%State{state | last_seq: last_seq}
|
|
end
|
|
end
|
|
|
|
def next_recipe(current, 0, _recipes), do: current
|
|
|
|
def next_recipe(current, steps, recipes) do
|
|
next_recipe(recipes[current].next, steps - 1, recipes)
|
|
end
|
|
|
|
def next_recipes(%{first_elf: first_elf, second_elf: second_elf, recipes: recipes} = state) do
|
|
%State{
|
|
state
|
|
| first_elf: next_recipe(first_elf, recipes[first_elf].score + 1, recipes),
|
|
second_elf: next_recipe(second_elf, recipes[second_elf].score + 1, recipes)
|
|
}
|
|
end
|
|
|
|
# Set the target pointer when the target count is reached
|
|
def set_target(%{count: target, last: last} = state, target), do: %State{state | target: last}
|
|
def set_target(state, _target), do: state
|
|
|
|
def compute_recipes(%{count: count, target_count: target} = state) when count >= target + 10,
|
|
do: state
|
|
|
|
def compute_recipes(%{first_elf: elf1, second_elf: elf2, recipes: recipes} = state) do
|
|
combine_recipes(recipes[elf1].score, recipes[elf2].score)
|
|
|> Enum.reduce(state, &append_score/2)
|
|
|> next_recipes()
|
|
|> compute_recipes()
|
|
end
|
|
|
|
def next_ten_scores(_recipes, _current, scores, 10), do: scores
|
|
|
|
def next_ten_scores(recipes, current, scores, count) do
|
|
next = recipes[current].next
|
|
scores = scores <> Integer.to_string(recipes[next].score)
|
|
next_ten_scores(recipes, next, scores, count + 1)
|
|
end
|
|
|
|
def part1 do
|
|
state = init(330_121) |> compute_recipes()
|
|
next_ten_scores(state.recipes, state.target, "", 0)
|
|
end
|
|
|
|
# Part 2
|
|
# Produce recipes until the last 6 are the puzzle input
|
|
# Then the answer is the count before those 6
|
|
def find_sequence(%{desired_sequence: desired, last_seq: desired, count: count}) do
|
|
count - tuple_size(desired)
|
|
end
|
|
|
|
def find_sequence(%{first_elf: elf1, second_elf: elf2, recipes: recipes} = state) do
|
|
if rem(state.count, 100_000) == 0 do
|
|
IO.puts((state.last_seq |> Tuple.to_list() |> Enum.join()) <> " (#{state.count})")
|
|
end
|
|
|
|
combine_recipes(recipes[elf1].score, recipes[elf2].score)
|
|
|> Enum.reduce(state, &append_score/2)
|
|
|> next_recipes()
|
|
|> find_sequence()
|
|
end
|
|
|
|
# TODO both 1245 and the actual answer are +1. Fix.
|
|
def part2 do
|
|
IO.puts("tests...")
|
|
init(51589) |> find_sequence |> IO.puts()
|
|
init(1245) |> find_sequence |> IO.puts()
|
|
init(92510) |> find_sequence |> IO.puts()
|
|
init(59414) |> find_sequence |> IO.puts()
|
|
IO.puts("answer: (after about 20M items)...")
|
|
init(330_121) |> find_sequence
|
|
end
|
|
end
|