Day 05
Mix.install([
{:kino, "~> 0.7.0"}
])
IEx.Helpers.c("/Users/johnb/dev/2023adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input
# Note: when making the next template, something like this works well:
# `cat day04.livemd | sed 's/03/04/' > day04.livemd`
#
Installation and Data
input_p1example = Kino.Input.textarea("Example Data")
input_p1puzzleInput = Kino.Input.textarea("Puzzle Input")
input_source_select =
Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
p1data = fn ->
(Kino.Input.read(input_source_select) == :example &&
Kino.Input.read(input_p1example)) ||
Kino.Input.read(input_p1puzzleInput)
end
Part 1
defmodule Day05 do
def make_ranges(lines) do
lines
|> Enum.reduce([], fn line, acc ->
[dest_start, src_start, len] =
String.split(line, ~r/\W/, trim: true)
|> Enum.map(&String.to_integer/1)
[%{src: Range.new(src_start, src_start + len - 1), dest: dest_start, len: len} | acc]
end)
|> Enum.reverse()
end
def translate(seed, mappings, which_map) do
map_list = mappings["#{which_map} map:"]
# |> IO.inspect()
case Enum.find(map_list, fn item ->
seed in item.src
end) do
nil -> seed
item -> seed - item.src.first + item.dest
end
end
def parse_raw_seeds_and_mappings(text) do
[seeds | mappings] = AOC.as_doublespaced_paragraphs(text)
# IO.inspect(seeds)
seeds =
String.split(seeds, ~r/\W/, trim: true)
|> Enum.slice(1..-1)
|> Enum.map(&String.to_integer/1)
mappings =
mappings
|> Enum.reduce(%{}, fn mapping, acc ->
[name | maps] = String.split(mapping, "\n", trim: true)
Map.put_new(acc, name, make_ranges(maps))
end)
[seeds, mappings]
end
def solve(text) do
[seeds, mappings] = parse_raw_seeds_and_mappings(text)
seeds
|> Enum.map(fn seed ->
seed
|> translate(mappings, "seed-to-soil")
|> translate(mappings, "soil-to-fertilizer")
|> translate(mappings, "fertilizer-to-water")
|> translate(mappings, "water-to-light")
|> translate(mappings, "light-to-temperature")
|> translate(mappings, "temperature-to-humidity")
|> translate(mappings, "humidity-to-location")
end)
|> Enum.min()
end
def split_and_map(range, mapping_list) do
# assume only one mapping will match
mapping_list
|> Enum.reduce_while(range, fn mapping, acc ->
# pre-create what we need, whether we use it or not
delta = mapping.dest - mapping.src.first
mapit = fn r -> Range.new(r.first + delta, r.last + delta) end
left = Range.new(range.first, mapping.src.first - 1)
right = Range.new(mapping.src.last + 1, range.last)
remapped =
Range.new(
max(mapping.src.first, range.first),
min(mapping.src.last, range.last)
)
|> mapit.()
case {range.first < mapping.src.first, range.last < mapping.src.first,
range.first > mapping.src.last, range.last > mapping.src.last} do
{true, true, _, _} ->
{:cont, acc}
{_, _, true, _} ->
{:cont, acc}
{true, false, false, true} ->
{:halt,
[split_and_map(left, mapping_list), remapped, split_and_map(right, mapping_list)]}
{true, false, false, false} ->
{:halt, [split_and_map(left, mapping_list), remapped]}
{false, false, false, true} ->
{:halt, [remapped, split_and_map(right, mapping_list)]}
{false, false, false, false} ->
{:halt, remapped}
end
end)
end
def translate2(seed_ranges, mappings, which_map) do
# IO.puts("\ntranslate2(#{inspect(seed_ranges)},,#{inspect(which_map)})")
mapping_list = mappings["#{which_map} map:"]
seed_ranges
|> List.flatten()
|> Enum.map(fn seed_range ->
seed_range
|> split_and_map(mapping_list)
end)
end
def solve2(text) do
[seeds, mappings] = parse_raw_seeds_and_mappings(text)
initial_seed_ranges =
Enum.chunk_every(seeds, 2)
|> Enum.map(fn [start, len] -> Range.new(start, start + len - 1) end)
initial_seed_ranges
|> translate2(mappings, "seed-to-soil")
|> translate2(mappings, "soil-to-fertilizer")
|> translate2(mappings, "fertilizer-to-water")
|> translate2(mappings, "water-to-light")
|> translate2(mappings, "light-to-temperature")
|> translate2(mappings, "temperature-to-humidity")
|> translate2(mappings, "humidity-to-location")
|> List.flatten()
|> Enum.map(fn range -> range.first end)
|> Enum.min()
end
end
p1data.()
|> Day05.solve()
|> IO.inspect(label: "\n*** Part 1 solution (example: 35)")
# 389056265
p1data.()
|> Day05.solve2()
|> IO.inspect(label: "\n*** Part 2 solution (example: 46)")
# 137516820