lud

lud

Advent of Code 2023 - Day 23

For part 2 I transformed the map into a graph because I wanted to see it and check if there was a bottleneck point or something.

But no, so I guess the code for P1 would have worked too, the only difference is that I run all possible states 1 step ahead to the next intersection (instead of running just one), and then I keep only the 3000 longest.

https://github.com/lud/adventofcode/blob/main/lib/solutions/2023/day23.ex

Most Liked

bjorng

bjorng

Erlang Core Team

According to Wikipedia, the longest path problem is NP-hard. Fortunately, the reduced graph (with all straight-line garden paths reduced into single vertices) is sufficiently small that it is practical to calculate the length of all possible graphs. After optimizing my solution it solves both parts in 16 seconds on my computer.

I assume that there is a divide-and-conquer approach for finding the longest path for this particular graph much faster, but I didn’t pursue it.

https://github.com/bjorng/advent-of-code-2023/blob/main/day23/lib/day23.ex

midouest

midouest

Similar to everyone else, I built a graph for the map by finding the junctions and the edges between them. I initially thought that I would need to handle both directed and undirected edges for part 1. However, I rendered the map and the junctions with Kino.HTML and saw that all of the edges were directed. Funny that my code would have needed almost no changes for part 2 if I had implemented that behavior in part 1!

Part 1
defmodule Part1 do
  @deltas [{1, 0}, {0, 1}, {-1, 0}, {0, -1}]

  def parse(input) do
    lines = String.split(input, "\n", trim: true)
    size = length(lines)

    map =
      for {line, y} <- Enum.with_index(lines),
          {char, x} <- String.to_charlist(line) |> Enum.with_index(),
          char != ?#,
          into: %{} do
        {{y, x}, char}
      end

    {map, size}
  end

  def find_nodes(map, size) do
    map
    |> Map.keys()
    |> Enum.filter(fn {y, x} ->
      y == 0 or y == size - 1 or
        @deltas
        |> Enum.map(fn {dy, dx} -> {y + dy, x + dx} end)
        |> Enum.count_until(fn {y, x} -> Map.has_key?(map, {y, x}) end, 3) == 3
    end)
  end

  def find_edges(map, nodes, directed \\ true), do: find_edges(map, nodes, directed, nodes, %{})
  def find_edges(_, _, _, [], acc), do: acc

  def find_edges(map, nodes, directed, [node | rest], acc) do
    acc =
      for delta <- @deltas,
          {start, finish} <- find_edge(map, nodes, directed, node, delta),
          reduce: acc do
        acc ->
          Map.update(acc, start, [finish], fn existing ->
            [finish | existing]
            |> Enum.uniq()
          end)
      end

    find_edges(map, nodes, directed, rest, acc)
  end

  def find_edge(map, nodes, directed, start, delta),
    do: find_edge(map, nodes, directed, start, 0, start, [delta])

  def find_edge(_, _, _, _, _, _, []), do: []

  def find_edge(map, nodes, directed, start, len, {y1, x1} = prev, [{dy1, dx1} | prev_deltas]) do
    next = {y1 + dy1, x1 + dx1}
    char = map[next]

    cond do
      char == nil ->
        find_edge(map, nodes, directed, start, len, prev, prev_deltas)

      directed and
          ((char == ?^ and dy1 == 1) or
             (char == ?< and dx1 == 1) or
             (char == ?v and dy1 == -1) or
             (char == ?> and dx1 == -1)) ->
        []

      Enum.member?(nodes, next) ->
        if directed,
          do: [{start, {next, len}}],
          else: [{start, {next, len}}, {next, {start, len}}]

      true ->
        next_deltas = Enum.reject(@deltas, fn {dy2, dx2} -> dy2 == -dy1 and dx2 == -dx1 end)
        find_edge(map, nodes, directed, start, len + 1, next, next_deltas)
    end
  end

  def find_paths(edges, size), do: find_paths(edges, size, [[{{0, 1}, 0}]], [])
  def find_paths(_, _, [], acc), do: acc

  def find_paths(edges, size, [[{{y, _} = prev, _} | _] = path | rest], acc) do
    if y == size - 1 do
      find_paths(edges, size, rest, [path | acc])
    else
      next =
        edges[prev]
        |> Enum.reject(fn {neighbor, _} ->
          Enum.any?(path, fn {visited, _} -> neighbor == visited end)
        end)
        |> Enum.map(&[&1 | path])

      find_paths(edges, size, next ++ rest, acc)
    end
  end

  def path_length(path) do
    path
    |> Enum.map(&elem(&1, 1))
    |> Enum.sum()
    |> Kernel.+(length(path) - 1)
  end

  def html(map, size, nodes \\ []) do
    text =
      for y <- 0..(size - 1) do
        for x <- 0..(size - 1) do
          case map[{y, x}] do
            nil ->
              ?#

            char ->
              if Enum.member?(nodes, {y, x}) do
                ~c"<b>O</b>"
              else
                char
              end
          end
        end
        |> List.flatten([?\n])
      end
      |> Enum.join()

    color = if length(nodes) > 0, do: "gray", else: "lightgray"

    Kino.HTML.new("""
    <style>
      html {
        font-size: 7px;
      }
      pre {
        color: #{color};
        background-color: black;
        padding: 5px;
        line-height: 1em;
        letter-spacing: 0.3em;
      }
      b {
        color: white;
      }
    </style>
    <pre><code>#{text}</code></pre>
    """)
  end
end
{map, size} = Part1.parse(input)
nodes = Part1.find_nodes(map, size)

Kino.render(Part1.html(map, size, nodes))

edges = Part1.find_edges(map, nodes)
paths = Part1.find_paths(edges, size)

paths
|> Enum.map(&Part1.path_length/1)
|> Enum.max()
Part 2
edges = Part1.find_edges(map, nodes, false)
paths = Part1.find_paths(edges, size)

paths
|> Enum.map(&Part1.path_length/1)
|> Enum.max()

Where Next?

Popular in Challenges Top

woolfred
It is that time of the year again: Advent of Code 2022 :christmas_tree: Day 1 Leaderboard:
New
ehayun
I have 2 arrays: a1 can be any combination of value or nil like that a1 = [1,nil,3] and array 2 the same a2 = [4,2, nil] How do I com...
New
coen.bakker
Since I started using Elixir, I have benefited greatly from being able to study various open-source projects. The codebase of LiveBook, i...
New
liamcmitchell
A frustrating one for me. I spent a long time trying to understand why some combinations resulted in fewer presses and struggled to keep ...
New
bjorng
Note: This topic is to talk about Day 25 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can joi...
New
adamu
Nobody’s doing Advent of Code this year? :grinning_face_with_smiling_eyes: I might do the first week or so. For Day 1, first I solved i...
New
Aetherus
This topic is about Day 16 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/l...
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
Aetherus
This topic is about Day 7 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/le...
New
christhekeele
Thought I’d kick today’s thread off! Parsing Enum rocks, so most of my code was actually in parsing input. ▶ Preprocessing input Part 1...
New

Other popular topics Top

Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
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
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

We're in Beta

About us Mission Statement