defmodule Aoc2023.Day5 do use Aoc2023 defp numbers(str), do: str |> String.split() |> Enum.map(&String.to_integer/1) def parse(input) do [seeds | maps] = input |> String.split("\n\n") { seeds |> String.split(": ") |> Enum.at(1) |> numbers, maps |> Enum.map(pipe( String.split("\n") |> tail |> Enum.map(pipe( numbers |> List.to_tuple() |> then(fn {dst, src, len} -> {src .. (src + len - 1), dst - src} end) )) )) } end defp split_range_on(r1, r2) do cond do Range.disjoint?(r1, r2) -> {[r1], []} r1.first < r2.first && r1.last < r2.last -> {a, b} = Range.split(r1, r2.first - r1.first) {[a], [b]} r1.first > r2.first && r1.last > r2.last -> {a, b} = Range.split(r1, r1.first - r2.first) {[b], [a]} r1.first >= r2.first && r1.last <= r2.last -> {[], [r1]} r1.first <= r2.first && r1.last >= r2.last -> {pre, rest} = Range.split(r1, r2.first - r1.first) {mid, last} = Range.split(rest, r2.last - rest.first) {[pre, last], [mid]} end end defp map(loc, map) do {unmapped, mapped} = for {src, offset} <- map, reduce: {[loc], []} do {unmapped, mapped} -> for range <- unmapped, reduce: {[], mapped} do {unmapped_, mapped_} -> {unmapped, mapped} = split_range_on(range, src) { unmapped_ ++ unmapped, mapped_ ++ Enum.map(mapped, &(Range.shift(&1, offset))) } end end unmapped ++ mapped end defp run(seeds, maps) do for seed <- seeds do for map <- maps, reduce: [seed] do loc -> loc |> Enum.flat_map(pipe(map(map))) end end |> Enum.flat_map(&id/1) |> Enum.min_by(&(&1.first)) |> then(&(&1.first)) end def part1({seeds, maps}) do run(seeds |> Enum.map(&(&1..&1)), maps) end def part2({seeds, maps}) do run( seeds |> Enum.chunk_every(2) |> Enum.map(fn [s, len] -> s..(s + len - 1) end), maps ) end end