Day 5: If You Give A Seed A Fertilizer – Advent of Code 2023
input =
File.stream!("/Users/pw/src/weiland/adventofcode/2023/input/05.txt")
|> Enum.join("")
# |> Stream.map(&String.trim/1)
test_input =
"seeds: 79 14 55 13
seed-to-soil map:
50 98 2
52 50 48
soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15
fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4
water-to-light map:
88 18 7
18 25 70
light-to-temperature map:
45 77 23
81 45 19
68 64 13
temperature-to-humidity map:
0 69 1
1 0 69
humidity-to-location map:
60 56 37
56 93 4"
Parsing
parse_from_stream = fn raw_almanac ->
seeds =
raw_almanac
|> Stream.take(1)
|> Enum.to_list()
|> Enum.at(0)
|> then(fn "seeds: " <> seeds -> seeds end)
|> String.split(" ")
|> Enum.map(&String.to_integer/1)
maps =
raw_almanac
# remove seeds
|> Stream.drop(1)
|> Stream.reject(&(&1 == ""))
%{seeds: seeds, maps: maps}
end
parse_string = fn raw_almanac ->
["seeds: " <> seeds | maps] = String.split(raw_almanac, "\n\n")
maps =
maps
|> Enum.map(&String.split(&1, "\n"))
|> Enum.map(fn [map | numbers] ->
[[_match, from, to]] = Regex.scan(~r/(.*)-to-(.*) map:/, map)
ins =
numbers
|> Enum.reject(fn l -> l == "" end)
|> Enum.map(fn l -> String.split(l, " ") |> Enum.map(&String.to_integer/1) end)
[from, to, ins]
end)
%{seeds: String.split(seeds, " ") |> Enum.map(&String.to_integer/1), maps: maps}
end
Part One
find_min_location = fn %{seeds: seeds, maps: maps} ->
Enum.reduce(maps, seeds, fn [_s, _d, map], acc ->
Enum.map(acc, fn seed ->
Enum.reduce_while(map, seed, fn [d, s, l], _acc ->
if s <= seed && seed < s + l do
{:halt, d + (seed - s)}
else
{:cont, seed}
end
end)
end)
end)
|> Enum.min()
end
part_one = fn input ->
input
|> parse_string.()
|> find_min_location.()
end
IO.inspect(part_one.(test_input) == 35, label: "Smoke test part one")
part_one.(input)
Part Two
res = fn %{seeds: seeds, maps: blocks} ->
map_matrix =
blocks
|> Enum.map(fn [_, _, maps] ->
Enum.map(maps, fn [d, s, l] ->
%{
destination_start: d,
destination_end: d + l - 1,
source_start: s,
source_end: s + l - 1
}
end)
end)
map_matrix
|> Enum.with_index()
|> Enum.map(fn {maps, i} ->
maps
|> Enum.map(fn m ->
Enum.slice(map_matrix, 0, i + 1)
# reduce right
|> Enum.reverse()
|> Enum.reduce(m.destination_start, fn maps, acc ->
n = Enum.find(maps, fn m -> acc >= m.destination_start && acc <= m.destination_end end)
if n, do: n.source_start + (acc - n.destination_start), else: acc
end)
end)
end)
|> List.flatten()
|> Enum.filter(fn seed ->
seeds
|> Enum.chunk_every(2)
|> Enum.any?(fn [s, len] -> seed >= s && seed < s + len end)
end)
|> Enum.map(fn seed ->
map_matrix
|> Enum.reduce(seed, fn maps, acc ->
m = Enum.find(maps, fn m -> acc >= m.source_start && acc <= m.source_end end)
if m, do: m.destination_start + (acc - m.source_start), else: acc
end)
end)
|> Enum.min()
end
part_two = fn input ->
input
|> parse_string.()
|> res.()
end
IO.inspect(part_two.(test_input) == 46, label: "Smoke test part one")
part_two.(input)