Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 05

2023/elixir/day05.livemd

Day 05

Mix.install([
  {:kino, "~> 0.11.0"}
])

Part 1

content =
  Kino.FS.file_path("input05.txt")
  |> File.read!()
test = """
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
"""
[seeds | maps] = String.split(content, "\n\n", trim: true)
"seeds: " <> ids = seeds
ids = ids |> String.split(" ", trim: true) |> Enum.map(&amp;(&amp;1 |> Integer.parse() |> elem(0)))
ranges =
  maps
  |> Enum.map(fn map ->
    map
    |> String.split()
    |> Enum.drop(2)
    |> Enum.map(&amp;(&amp;1 |> Integer.parse() |> elem(0)))
    |> Enum.chunk_every(3)
    |> Enum.map(fn [destination, source, range] ->
      {source..(source + range - 1), destination - source}
    end)
  end)
ids
|> Enum.map(fn id ->
  Enum.reduce(ranges, id, fn rs, id ->
    range = Enum.find(rs, fn {range, _diff} -> Enum.member?(range, id) end)

    case range do
      nil -> id
      {_range, diff} -> id + diff
    end
  end)
end)
|> Enum.min()

Part 2

seed_ranges =
  ids
  |> Enum.chunk_every(2)
  |> Enum.map(fn [from, lenght] ->
    from..(from + lenght - 1)
  end)
defmodule Almanac do
  def convert(seeds, map) do
    seeds
    |> split(endpoints(map), [])
    |> List.flatten()
    |> Enum.map(&amp;translate(&amp;1, map))
  end

  def translate(seeds, map) do
    range = Enum.find(map, fn {range, _diff} -> Enum.member?(range, seeds.first) end)

    case range do
      nil -> seeds
      {_range, diff} -> Range.shift(seeds, diff)
    end
  end

  def split(range, [], result) do
    Enum.reverse([range | result])
  end

  def split(range, [splitter | splitters], result) do
    if splitter > range.first &amp;&amp; splitter < range.last do
      {left, right} = Range.split(range, splitter - range.first)
      split(right, splitters, [left | result])
    else
      split(range, splitters, result)
    end
  end

  def endpoints(map) do
    map |> Enum.flat_map(fn {range, _diff} -> [range.first, range.last] end) |> Enum.sort()
  end
end
seed_ranges
|> Enum.flat_map(fn seed_range ->
  ranges
  |> Enum.reduce([seed_range], fn map, seeds ->
    Enum.flat_map(seeds, &amp;Almanac.convert(&amp;1, map))
  end)
end)
|> Enum.min_by(&amp; &amp;1.first)
|> then(&amp; &amp;1.first)