Day 5: If You Give A Seed A Fertilizer
Mix.install([:kino])
input = Kino.Input.textarea("Please paste your input:")
Part 1
https://adventofcode.com/2023/day/5
[seeds_str | maps_str] =
input
|> Kino.Input.read()
|> String.split("\n\n", trim: true)
seeds =
seeds_str
|> String.trim_leading("seeds: ")
|> String.split(" ")
|> Enum.map(&String.trim/1)
|> Enum.map(&String.to_integer/1)
maps =
maps_str
|> Enum.map(fn map_str ->
[map_name | map_values_str] = map_str |> String.split("\n")
%{"from" => from, "to" => to} =
Regex.named_captures(~r/(?.+)-to-(?.+) map:/, map_name)
map_values =
map_values_str
|> Enum.map(fn map_value_str ->
map_value_str |> String.split(" ") |> Enum.map(&String.to_integer/1)
end)
%{from: from, to: to, map_values: map_values}
end)
mappers =
maps
|> Enum.map(fn %{map_values: map_values} ->
fn input ->
map_values
|> Enum.find_value(fn [destination_start, source_start, range_length] ->
case input in source_start..(source_start + (range_length - 1)) do
false -> nil
true -> input + destination_start - source_start
end
end)
|> then(fn
nil -> input
output -> output
end)
end
end)
mappers
|> Enum.reduce(seeds, fn mapper, values -> values |> Enum.map(mapper) end)
|> Enum.min()
Part 2
https://adventofcode.com/2023/day/5#part2
# range: [start, end)
seed_ranges =
seeds_str
|> String.trim_leading("seeds: ")
|> String.split(" ")
|> Enum.map(&String.trim/1)
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(2)
|> Enum.map(fn [a, b] -> {a, a + b} end)
map_values_list =
maps
|> Enum.map(&(&1.map_values |> Enum.sort_by(fn [_, source_start, _] -> source_start end)))
map_values_list
|> Enum.reduce(seed_ranges, fn map_values, ranges ->
new_ranges =
ranges
|> Enum.flat_map(fn range ->
map_values
|> Enum.reduce_while(
{range, _new_ranges = []},
fn [destination_start, source_start, range_length], {{first, last}, new_ranges} ->
source_end = source_start + range_length
map_diff = destination_start - source_start
{ramain_range, new_ranges} =
cond do
# disjoint
last <= source_start ->
{nil, [{first, last} | new_ranges]}
first >= source_end ->
{{first, last}, new_ranges}
# intersection
first < source_start and last <= source_end ->
{nil,
[
{first, source_start},
{source_start + map_diff, last + map_diff} | new_ranges
]}
first >= source_start and last >= source_end ->
{{source_end, last}, [{first + map_diff, source_end + map_diff} | new_ranges]}
# include
first < source_start and last >= source_end ->
{{source_end, last},
[
{first, source_start},
{source_start + map_diff, source_end + map_diff}
| new_ranges
]}
# included
first >= source_start and last <= source_end ->
{nil, [{first + map_diff, last + map_diff} | new_ranges]}
end
case {ramain_range, new_ranges} do
{nil, new_ranges} -> {:halt, {nil, new_ranges}}
{remain_range, new_ranges} -> {:cont, {remain_range, new_ranges}}
end
end
)
|> case do
{nil, new_ranges} -> new_ranges
{remain_range, new_ranges} -> [remain_range | new_ranges]
end
end)
new_ranges
end)
|> Enum.min_by(fn {first, _} -> first end)
|> elem(0)