bjorng

bjorng

Erlang Core Team

Advent of Code 2024 - Day 16

After getting the correct answer for both parts, I spent some time optimizing the execution time. Now the combined time for both parts is 3.4 seconds.

Most Liked

7empest

7empest

I used Dijkstra’s shortest path algorithm for this question and implemented this using a min heap. Runtime for both the problems was around 30ms.
My solution for both parts: AdventOfCodeElixir/lib/advent/year2024/day16.ex at main · divxvid/AdventOfCodeElixir · GitHub

Benchee results:

Name ips average deviation median 99th %
part_1 29.20 34.25 ms ±11.38% 33.76 ms 50.57 ms
part_2 29.84 33.51 ms ±13.54% 32.51 ms 62.69 ms

sevenseacat

sevenseacat

Author of Ash Framework

For part 1, I used a fairly straightforward priority queue implementation - we’ve had quite a few puzzles like this before.

Part 2 was an interesting one… I ended up implementing some weird “store how we got here” logic as different paths reached the same point and then backtracking over all of those once I found the fastest solution.

I think it might not work correctly if there are two paths to the destination that don’t converge before the destination, but I don’t have that case in any of my inputs so yay!

Name                     ips        average  deviation         median         99th %
day 16, part 1         12.91       77.43 ms     ±5.49%       77.14 ms       86.71 ms
day 16, part 2         13.07       76.51 ms     ±5.00%       76.09 ms       85.69 ms

Now I have some boxes from yesterday to go and argue with some more…

sevenseacat

sevenseacat

Author of Ash Framework

Yeah the puzzles aren’t going anywhere, do them at your leisure :slight_smile:

(I stopped a few days ago because a) Christmas and b) 40 degree weather the last few days so my brain is cooked. Might pick it up again when it cools down)

ken-kost

ken-kost

I have no desire nor time anymore to slam my head on these harder and harder challenges. Last time I implemented dijkstra was in collage in Java. :dizzy_face:
I feel I’m not there yet skill wise to solve these challenges efficiently and in satisfactory time. Luckily, I have heads of you fine folks to slam right through. :ram:
Again I learned from your code @bjorng. I do vibe with your style. :star: First time playing with :gb_sets also. :muscle: I changed some things per my liking but the code is pretty much the same.

defmodule Aoc2024.Solutions.Y24.Day16 do
  alias AoC.Input

  def parse(input, _part) do
    input
    |> Input.stream!(trim: true)
    |> Stream.with_index()
    |> Enum.reduce({{}, {}, %{}}, fn {line, x}, acc ->
      line
      |> String.to_charlist()
      |> Enum.with_index()
      |> Enum.reduce(acc, fn
        {?S, y}, {_start, finish, map} -> {{x, y}, finish, Map.put(map, {x, y}, ?.)}
        {?E, y}, {start, _finish, map} -> {start, {x, y}, Map.put(map, {x, y}, ?E)}
        {e, y}, {start, finish, map} -> {start, finish, Map.put(map, {x, y}, e)}
      end)
    end)
  end

  def part_one({start, finish, map}) do
    element = {_score = 0, _position = start, _direction = {0, 1}, _passed = MapSet.new()}
    set = :gb_sets.singleton(element)
    find_paths(set, map, finish, _visited = MapSet.new(), _max_score = nil)
  end

  def part_two({start, finish, map}) do
    element = {_score = 0, _position = start, _direction = {0, 1}, _passed = MapSet.new()}
    set = :gb_sets.singleton(element)

    find_paths(set, map, finish, _visited = MapSet.new(), _max_score = :infinity)
    |> Enum.reduce(MapSet.new(), fn set, acc -> MapSet.union(acc, set) end)
    |> MapSet.size()
  end

  defp find_paths(set, map, finish, visited, max_score) do
    case if not :gb_sets.is_empty(set), do: :gb_sets.take_smallest(set) do
      nil ->
        []

      # part 1 case
      {{score, ^finish, _direction, _passed}, _set} when is_nil(max_score) ->
        score

      # part 2 case
      {{score, ^finish, _direction, passed}, set} ->
        if score <= max_score do
          max_score = min(max_score, score)
          [MapSet.put(passed, finish) | find_paths(set, map, finish, visited, max_score)]
        else
          []
        end

      {{score, position, direction, passed}, set} ->
        visited = MapSet.put(visited, {position, direction})
        passed = if is_nil(max_score), do: MapSet.new(), else: MapSet.put(passed, position)
        forward_path = forward_path(map, finish, score, position, direction, passed)
        rotated_paths = rotated_paths(map, score, position, direction, passed)
        paths = [forward_path | rotated_paths]
        set = update_set(paths, set, map, visited)
        find_paths(set, map, finish, visited, max_score)
    end
  end

  defp forward_path(map, finish, score, {x, y} = _position, {xd, yd} = direction, passed) do
    {xn, yn} = next_position = {x + xd, y + yd}
    next_left_position = {xn - yd, yn + xd}
    next_right_position = {xn + yd, yn - xd}

    if next_position != finish and
         Map.get(map, next_position) != ?# and
         Map.get(map, next_left_position) == ?# and
         Map.get(map, next_right_position) == ?# do
      passed = MapSet.put(passed, next_position)
      forward_path(map, finish, score + 1, next_position, direction, passed)
    else
      {score + 1, next_position, direction, passed}
    end
  end

  defp rotated_paths(map, score, {x, y} = position, {xd, yd} = _direction, passed) do
    left_position = {x - yd, y + xd}
    left_path = {score + 1000, position, {-yd, xd}, passed}
    right_position = {x + yd, y - xd}
    right_path = {score + 1000, position, {yd, -xd}, passed}
    paths = if Map.get(map, left_position) != ?#, do: [left_path], else: []
    if Map.get(map, right_position) != ?#, do: [right_path | paths], else: paths
  end

  defp update_set(paths, set, map, visited) do
    Enum.reduce(paths, set, fn {_score, position, direction, _passed} = element, set ->
      cond do
        Map.get(map, position) == ?# -> set
        MapSet.member?(visited, {position, direction}) -> set
        true -> :gb_sets.add(element, set)
      end
    end)
  end
end
dimitarvp

dimitarvp

I heard people complain that AoC basically monopolizes their free time around the holiday. Yikes. I’ll still give it a go after my dust settles but, not a good sign. Thanks for confirming the impression.

Where Next?

Popular in Challenges Top

LostKobrakai
This topic is about Day 9 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/le...
New
antoine-duchenet
Everything went smoothly today. Nothing to change to solve part 2 because I already used memoization for part 1 (it looked like an AoC e...
New
LostKobrakai
This one has been quite the ride. Struggled at first to find a good data format to suite the problem. I really like how that turned out b...
New
adamu
I said I was on a break, but I took a sneak peak and it looked fun so… Part 1 completes in half a millisecond with a single pass of the ...
New
bjorng
Note: This topic is to talk about Day 13 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can joi...
New
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 expensi...
New
code-shoily
Just did part 1. Part 2 seems to be demanding too much of my reading time so will get to that after I am done with some chores. Oh here ...
New
stevensonmt
Reasonably pleased with my solution. The bitstring packet problems are so well suited to Erlang/Elixir it’s almost not fair. defmodule D...
New
bjorng
This topic is about Day 15 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums): https://adve...
New
code-shoily
Here’s my day 3 code This was quite easy. I was afraid Part 2 would be “un-regex-able” and was preparing for hand crafting automata bu...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement