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.

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.

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

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
sasajuric
Note: This topic is to talk about Day 12 of the Advent of Code. For general discussion about the Advent of Code 2018 and links to topics...
New
adamu
Nobody’s doing Advent of Code this year? :smile: I might do the first week or so. For Day 1, first I solved it using regular expression...
New
bjorng
Note: This topic is to talk about Day 12 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can joi...
New
igorb
I found today a bit tedious: advent-of-code-2024/lib/advent_of_code2024/day15.ex at main · ibarakaiev/advent-of-code-2024 · GitHub.
New
bjorng
Note: This topic is to talk about Day 3 of the Advent of Code 2019 . There is a private leaderboard for elixirforum members. You can jo...
New
Aetherus
The second part of today’s puzzle is very misleading. FYI, each of the ghosts has only one possible position that ends with a "Z" on its...
New
bjorng
Note: This topic is to talk about Day 23 of the Advent of Code. For general discussion about the Advent of Code 2018 and links to topics...
New
bjorng
Note: This topic is to talk about Day 18 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can joi...
New
connorlay
Note by the Moderators: This topic is for general discussion about the Advent of Code 2018. To prevent people from being spoiled about s...
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
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
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
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

We're in Beta

About us Mission Statement