christhekeele
Setting this down for the night, as after a quick naive solve for quick part 1 I realize that part 2 is by design computationally expensive to solve by repurposing the code used in part 1. Will update if I get part 2 working fast, I think I have the right idea in mind!
Input
Processing
I generated a list of seeds, and a map-of-maps with each key being a transform type, and each value being a map with input ranges as keys and output ranges as values.
Types
@type seeds :: [integer()]
@type maps :: %{type :: atom => mapping}
@type mapping :: %{Range.t() => Range.t()}
@type input :: {seeds, maps}
Example Input
{[79, 14, 55, 13],
%{
seed_to_soil: %{50..97 => 52..99, 98..99 => 50..51},
soil_to_fertilizer: %{0..14 => 39..53, 15..51 => 0..36, 52..53 => 37..38},
fertilizer_to_water: %{
0..6 => 42..48,
7..10 => 57..60,
11..52 => 0..41,
53..60 => 49..56
},
water_to_light: %{18..24 => 88..94, 25..94 => 18..87},
light_to_temperature: %{45..63 => 81..99, 64..76 => 68..80, 77..99 => 45..67},
temperature_to_humidity: %{0..68 => 1..69, 69..69 => 0..0},
humidity_to_location: %{56..92 => 60..96, 93..96 => 56..59}
}}
Source code available here.
defmodule AoC.Day.Five.Input do
def parse(input_file \\ System.fetch_env!("INPUT_FILE")) do
[[seeds] | maps] =
input_file
|> File.read!()
|> String.split("\n\n")
|> Enum.reject(&(&1 == ""))
|> Enum.map(&String.split(&1, "\n"))
|> Enum.map(fn line -> Enum.reject(line, &(&1 == "")) end)
<<"seeds: ">> <> seeds = seeds
seeds = seeds |> String.split(" ") |> Enum.map(&String.to_integer/1)
maps = Map.new(maps, &parse_map/1)
{seeds, maps}
end
def parse_map([name | mappings]) do
name =
name
|> String.trim_trailing(" map:")
|> String.replace("-", "_")
|> String.to_atom()
mappings = Map.new(mappings, &parse_mapping/1)
{name, mappings}
end
def parse_mapping(mapping) do
[output, input, length] =
mapping
|> String.split(" ", parts: 3)
|> Enum.map(&String.to_integer/1)
{Range.new(input, input + length - 1, 1), Range.new(output, output + length - 1, 1)}
end
end
Part One
Solution
The benefit of using ranges here is immediately apparent, as I never have to hydrate a full one to lookup a seed’s location. Instead I just scan through input ranges and use an offset to get the correct output of a mapping. Reduce this through all the mappings, and you’re home free.
Source code available here.
defmodule AoC.Day.Five.Part.One do
def solve({seeds, maps}) do
seeds
|> Enum.map(&lookup_seed_location(&1, maps))
|> Enum.min()
end
def lookup_seed_location(seed, maps) do
[
:seed_to_soil,
:soil_to_fertilizer,
:fertilizer_to_water,
:water_to_light,
:light_to_temperature,
:temperature_to_humidity,
:humidity_to_location
]
|> Enum.reduce(seed, &perform_mapping(&2, &1, maps))
end
def perform_mapping(input, type, maps) do
{input, type, maps}
Enum.find_value(maps[type], input, fn {
input_range = input_start.._,
output_start.._
} ->
if input in input_range do
offset = input - input_start
output_start + offset
end
end)
end
end
Part Two
(Too Slow) Solution
Here we learn that our seed inputs are in fact seed ranges. Tempting to just expand those ranges into lists and push each seed through our single seed lookup from before. However, the full input uses ranges billions of seeds wide and this does not perform!
Clearly, a correct solution will need to be smarter about lookups. Here, using ranges obfuscates what we need to do—take an input start value and offset, and preserve that offset throughout our lookups to avoid ever actually having to materialize our input ranges. I’ll probably rewrite the input handler, and part 1 in terms of it, before tackling part 2 again. Perhaps even tomorrow!
Source code available here.
defmodule AoC.Day.Five.Part.Two do
import AoC.Day.Five.Part.One
def solve({seeds, maps}) do
seeds
|> seeds_to_seed_ranges()
|> Enum.map(&Range.to_list/1) # This is Bad™ actually
|> List.flatten()
|> Enum.map(&lookup_seed_location(&1, maps))
|> Enum.min()
end
def seeds_to_seed_ranges(seeds) do
seeds
|> Enum.chunk_every(2)
|> Enum.map(&Range.new(List.first(&1), List.first(&1) + List.last(&1) - 1, 1))
end
end
Trending in Challenges
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
lud
This problem scared me like here we go with big numbers … c’mon, day five!?
My solution for part 2 was to pass the initial ranges to each map. The top iterator is over the input maps because I felt it would be simpler to keep a list of ranges to translate at each step. But I guess it could be possible to pass each seed range to each map before translating the next seed range.
Anyway:
10..20and the mapper issource=0..15 dest=100..115then this returns a translated range100..105(for the10..15part) and an other range of16..20that was not matched by the mapper..firstand return that.first.With my input I have 153 ranges after the last step. It takes between 1 and 2 milliseconds for part 2 (I never have consistent times on my machine…)
Aetherus
Pretty fast solution using
:gb_trees.The keys in the trees are
{source_low, source_high}and the corresponding values are{destination_low, destination_high}(I don’t like the idea of length, so I just want to convert them to ranges, but comparison of ranges gives warning, so I just convert them to{low, high}).Maybe using
:gb_treesis an overkill. I realized that I can just sort them.Here’s the Livebook file:
https://github.com/Aetherus/advent-of-code/blob/master/2023/day-05.livemd
trnasistor
My beginner’s solution, Day 05 part 1
midouest
Part 2 left me wishing I had some version of the local accumulators proposal.
The triply-nested for-reduce comprehension in my solution was a real headache to think through.
https://github.com/midouest/advent-of-code-2023/blob/main/notebooks/day05.livemd
ramuuns
Took me a while to figure out how to split the ranges properly, but pretty happy with the 7ms runtime:
https://github.com/ramuuns/aoc/blob/master/2023/day-05.ex
shritesh
Ha. I was also wishing for the local accumulation proposal in Part 2. My colleagues are brute forcing this and materializing the ranges just made my 64GB RAM run out lol. I just ended up splitting the ranges and it runs instantaneously.
src: public-apps/05.livemd · shritesh/aoc2023 at main
deployed: https://shritesh-aoc2023.hf.space/apps/05/hhtya3nyedp7srni6f6gj3xsynsv3dt5gg2rrggwv43cm327
hauleth
You do not need to materialise ranges, as you can reduce them on line.
igorb
My solutions for today:
Same general idea as everyone else’s, except that I explicitly track which ranges have already been mapped. I was struggling with that part for a while (long enough to admit), but the example that helped me figure out the issue was:
input_range = %{start: 74, end: 87},range_map = %{source_range_start: 64, range_length: 13, destination_range_start: 68}. Splitting here will produce two ranges, one of which is a subset of another, but it’s important to keep in mind that the one that’s a subset is the final mapping, while the other one can get transformed. So really the mistake I made was thinking about each mapping within a round as a standalone transformation, whereas they all really should happen at the same time. The solution runs in about 4 ms withoutTask.async_stream/2, and in around 1 ms with it.seeplusplus
I struggled with this one a ton and really wanted to fix it without looking here for inspiration. I learned a lot from this one, can’t wait to see what others did. I’m floored that some finished this so quickly.
There are fair bit of util libraries in use here, but I think they are pretty self explanatory.
kwando
Nice, I came up with almost same solution. I was pleasantly surprised I got it right on the first try, kinda expected to chase off-by-1 errors all morning