diff --git a/lib/2023_day5.ex b/lib/2023_day5.ex new file mode 100644 index 0000000..38f4cb1 --- /dev/null +++ b/lib/2023_day5.ex @@ -0,0 +1,72 @@ +defmodule Mix.Tasks.Day5 do + use Mix.Task + + def run(_) do + IO.stream() |> parse() |> part1() |> IO.puts() + end + + def split_int_list(s, sep) do + s |> String.split(sep) |> Enum.map(fn n -> Integer.parse(n) |> elem(0) end) + end + + def parse_line("seeds: " <> seeds) do + {:seeds, seeds |> split_int_list(" ")} + end + + def parse_line(line) when line != "" do + make_range = fn line -> + [dest_start, source_start, len] = line |> split_int_list(" ") + {:range, {RangeUtil.from_start(dest_start, len), RangeUtil.from_start(source_start, len)}} + end + cap = Regex.run(~r/(\w+)-to-(\w+) map:/, line) + case cap do + [_, source, dest] -> {:map_key, { source |> String.to_atom(), dest |> String.to_atom() } } + nil -> make_range.(line) + end + end + + def parse(input) do + for line <- input, + line = line |> String.trim(), + line != "", + reduce: %{} do + acc -> + {type, data} = parse_line(line) + case type do + :seeds -> acc |> Map.put(type, data) + :map_key -> { source, dest } = data + acc + |> Map.update(:maps, %{source => {dest, []}}, fn maps -> maps |> Map.put(source, {dest, []}) end) + |> Map.put(:last_map_key, source) + :range -> %{last_map_key: last_map_key} = acc + acc |> Map.update!( + :maps, + fn maps -> maps + |> Map.update!(last_map_key, fn {dest, ranges} -> + {dest, [data | ranges]} + end) + end) + end + end + end + def get_location(:location, n, _) do + n + end + def get_location(source, n, maps) do + {dest, ranges} = maps |> Map.get(source) + transpose = case ranges |> Enum.find(fn {_, source} -> + n in source + end) do + {dest_range, source_range} ->RangeUtil.transpose(n, source_range, dest_range) + nil -> n + end + IO.puts({dest, transpose} |> inspect()) + get_location(dest, transpose, maps) + end + + def part1(state) do + for seed <- state.seeds do + get_location(:seed, seed, state.maps) + end |> Enum.min() + end +end diff --git a/test/2023_day5_test.exs b/test/2023_day5_test.exs new file mode 100644 index 0000000..b3ddb15 --- /dev/null +++ b/test/2023_day5_test.exs @@ -0,0 +1,78 @@ +defmodule Day5Tests do + use ExUnit.Case + + test "parse seeds correctly" do + assert Mix.Tasks.Day5.parse_line("seeds: 79 14 55 13") == {:seeds, [79, 14, 55, 13]} + end + + test "parse map line def" do + assert Mix.Tasks.Day5.parse_line("seed-to-soil map:") == {:map_key, { :seed, :soil }} + end + + test "parse map line fails" do + assert Mix.Tasks.Day5.parse_line("50 98 2") == {:range, {50..51, 98..99}} + end + + test "range util does not lie" do + assert (RangeUtil.from_start(120, 300) |> Enum.count()) === 300 + end + + test "range util transpose does not lie" do + assert RangeUtil.transpose(98, 98..99, 50..51) === 50 + end + + test "parse works" do + ex = ~s"seeds: 79 14 55 13 + +seed-to-soil map: +50 98 2 +52 50 48" + assert Mix.Tasks.Day5.parse(ex |> String.split("\n")) == %{ + :seeds => [79, 14, 55, 13], + :maps => %{:seed => {:soil, [{52..99, 50..97}, {50..51, 98..99}]}}, + :last_map_key => :seed + } + end + + test "parse doesn't crash with whole input" do + ex = ~s"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 +" + assert ex |> String.split("\n") |> Mix.Tasks.Day5.parse() |> Mix.Tasks.Day5.part1() == 35 + end +end