<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="348836" data-post-id="348836">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Aetherus" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Aetherus/120/17203_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Aetherus
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Thanks for your advice.</p>
<p>23s is not a typo.</p>
<p>Good to know the ETS table is allocated elsewhere than the process’s heap. I don’t know why changing <code>in</code> to <code>MapSet.member?/2</code> has a notable effect, isn’t that <code>Kernel.in/2</code> calls <code>Enumerable.member?/2</code> which in turn calls <code>MapSet.member/2</code>?</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="348836" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/advent-of-code-2024-day-6/67917/32">Post #31</a>
	                </div>
	            </div>
              <div id="likers-container-348836" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="348836"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #31"></div>
  </section>
</div>
    <div class="postbit" id="348839" data-post-id="348839">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="bjorng" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bjorng/120/13187_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  bjorng
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Erlang Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="Aetherus" data-post="32" data-topic="67917">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/aetherus/48/17203_2.png" class="avatar"> Aetherus:</div>
<blockquote>
<p>I don’t know why changing <code>in</code> to <code>MapSet.member?/2</code> has a notable effect, isn’t that <code>Kernel.in/2</code> calls <code>Enumerable.member?/2</code> which in turn calls <code>MapSet.member/2</code>?</p>
</blockquote>
</aside>
<p>I suppose that the overhead for the extra function calls is noticeable when <code>in</code> is called very frequently.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="348839" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/advent-of-code-2024-day-6/67917/33">Post #32</a>
	                </div>
	            </div>
              <div id="likers-container-348839" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="348839"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #32"></div>
  </section>
</div>
    <div class="postbit" id="348856" data-post-id="348856">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="jarlah" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jarlah/120/24789_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  jarlah
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>It was fun to wait aaaaall the way to the next morning only to find out</p>
<ol>
<li>answer was wrong</li>
<li>I should really comvert a list of positions into a map</li>
</ol>
<p><img src="https://forum.elixirforum.com/images/emoji/apple/rofl.png?v=15" title=":rofl:" class="emoji only-emoji" alt=":rofl:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="348856" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/advent-of-code-2024-day-6/67917/34">Post #33</a>
	                </div>
	            </div>
              <div id="likers-container-348856" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="348856"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #33"></div>
  </section>
</div>
    <div class="postbit" id="348874" data-post-id="348874">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="jarlah" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jarlah/120/24789_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  jarlah
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>i looked at your code and used some of your ideas and incoroporated them into my own code for Part2.</p>
<p><a href="https://github.com/jarlah/advent_of_code/blob/master/lib/2024/day_6/Part2.ex#L16" class="onebox" target="_blank" rel="noopener nofollow ugc">https://github.com/jarlah/advent_of_code/blob/master/lib/2024/day_6/Part2.ex#L16</a></p>
<p>My part1 worked without using map with coord keys. That worked perfectly with list of coords. This is my first AOC in 16 years since i started developing and i have NEVER gotten use of using maps for quick lookups. So its been digged way back into my memory as a go to solution … Been mostly frontend and backend, with “normal” problems. (ok, ok, maybe some few times, but seldom …)=\</p>
<p>it uses 17-18 seconds though on the full input (AMD Ryzen 1700, 8 cores, 16 virtual) .. could be my struct usage and patter matching maybe. EDIT: actually on github workflow it ran in 8 seconds .. so i guess its highly machine dependant</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="348874" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/advent-of-code-2024-day-6/67917/35">Post #34</a>
	                </div>
	            </div>
              <div id="likers-container-348874" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="348874"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #34"></div>
  </section>
</div>
    <div class="postbit" id="348901" data-post-id="348901">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="seeplusplus" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/seeplusplus/120/32772_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  seeplusplus
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>This was great knowledge, thanks! I tested it out and, indeed, using <code>:binary.at</code> was <strong>much</strong> faster than <code>String.at</code>. I get roughly equal runtime for part 2 with <code>:binary.at</code> as I do when I convert the input into a <code>Map</code> and use <code>Map.get</code>. Both of which are at least two orders of magnitude faster than <code>String.at</code> in this case.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="348901" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/advent-of-code-2024-day-6/67917/36">Post #35</a>
	                </div>
	            </div>
              <div id="likers-container-348901" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="348901"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #35"></div>
  </section>
</div>
    <div class="postbit" id="348908" data-post-id="348908">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="mwilsoncoding" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/mwilsoncoding/120/36815_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  mwilsoncoding
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I got some sleep yesterday, but that means I’m playing catch-up.</p>
<p>Here’s pt1 for me:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">#!/usr/bin/env elixir

defmodule Day6.Part1 do
  @westward_facing_guard ~c"&lt;" |&gt; List.first()
  @eastward_facing_guard ~c"&gt;" |&gt; List.first()
  @northward_facing_guard ~c"^" |&gt; List.first()
  @southward_facing_guard ~c"v" |&gt; List.first()

  @guard_chars [
    @northward_facing_guard,
    @southward_facing_guard,
    @eastward_facing_guard,
    @westward_facing_guard
  ]
  @obstacle ~c"#" |&gt; List.first()
  @open_space ~c"." |&gt; List.first()
  @seen ~c"X" |&gt; List.first()

  defp parse(str) do
    [row | _tail] =
      rows =
      str
      |&gt; String.split("\n")
      |&gt; Enum.map(&amp;to_charlist/1)

    height = Enum.count(rows)
    length = Enum.count(row)

    {x, y} =
      guard_position =
      rows
      |&gt; Enum.with_index()
      |&gt; Enum.reduce_while(
        {nil, nil},
        fn {charlist, y_index}, {nil, nil} -&gt;
          x_index =
            charlist
            |&gt; Enum.find_index(fn c -&gt;
              c in @guard_chars
            end)

          if is_nil(x_index), do: {:cont, {nil, nil}}, else: {:halt, {x_index, y_index}}
        end
      )

    guard_row =
      rows
      |&gt; Enum.at(y)

    orientation =
      guard_row
      |&gt; Enum.at(x)

    sanitized_guard_row =
      guard_row
      |&gt; Enum.map(fn
        c -&gt; if c in @guard_chars, do: @open_space, else: c
      end)

    rows = (rows |&gt; Enum.take(y)) ++ [sanitized_guard_row] ++ (rows |&gt; Enum.drop(y + 1))

    %{
      map: %{
        rows: rows,
        height: height,
        length: length
      },
      guard_position: guard_position,
      orientation: orientation
    }
  end

  @next_orientation %{
    @northward_facing_guard =&gt; @eastward_facing_guard,
    @eastward_facing_guard =&gt; @southward_facing_guard,
    @southward_facing_guard =&gt; @westward_facing_guard,
    @westward_facing_guard =&gt; @northward_facing_guard
  }

  defp mark_map(
         %{
           map: %{
             rows: rows,
             height: height,
             length: length
           },
           guard_position: {x, y} = guard_position,
           orientation: orientation
         } = state,
         acc
       ) do
    row_above =
      if y == 0, do: :off_the_map, else: y - 1

    row_below =
      if y == height - 1, do: :off_the_map, else: y + 1

    column_before =
      if x == 0, do: :off_the_map, else: x - 1

    column_after =
      if x == length - 1, do: :off_the_map, else: x + 1

    current_row =
      rows
      |&gt; Enum.at(y)

    current_value =
      current_row
      |&gt; Enum.at(x)

    marked_row = (current_row |&gt; Enum.take(x)) ++ [@seen] ++ (current_row |&gt; Enum.drop(x + 1))

    rows = (rows |&gt; Enum.take(y)) ++ [marked_row] ++ (rows |&gt; Enum.drop(y + 1))

    {guard_position, orientation} =
      case orientation do
        @northward_facing_guard -&gt;
          next_is_obstacle? =
            if row_above == :off_the_map do
              false
            else
              rows |&gt; Enum.at(row_above) |&gt; Enum.at(x) == @obstacle
            end

          if next_is_obstacle? do
            {guard_position, @next_orientation[orientation]}
          else
            {{x, row_above}, orientation}
          end

        @southward_facing_guard -&gt;
          next_is_obstacle? =
            if row_below == :off_the_map do
              false
            else
              rows |&gt; Enum.at(row_below) |&gt; Enum.at(x) == @obstacle
            end

          if next_is_obstacle? do
            {guard_position, @next_orientation[orientation]}
          else
            {{x, row_below}, orientation}
          end

        @eastward_facing_guard -&gt;
          next_is_obstacle? =
            if column_after == :off_the_map do
              false
            else
              rows |&gt; Enum.at(y) |&gt; Enum.at(column_after) == @obstacle
            end

          if next_is_obstacle? do
            {guard_position, @next_orientation[orientation]}
          else
            {{column_after, y}, orientation}
          end

        @westward_facing_guard -&gt;
          next_is_obstacle? =
            if column_before == :off_the_map do
              false
            else
              rows |&gt; Enum.at(y) |&gt; Enum.at(column_before) == @obstacle
            end

          if next_is_obstacle? do
            {guard_position, @next_orientation[orientation]}
          else
            {{column_before, y}, orientation}
          end
      end

    increase = if current_value == @seen, do: 0, else: 1

    {%{
       state
       | map: %{state.map | rows: rows},
         guard_position: guard_position,
         orientation: orientation
     }, increase + acc}
  end

  defp count_newly_traversed(state) do
    Stream.repeatedly(fn -&gt; nil end)
    |&gt; Enum.reduce_while(
      {state, 0},
      fn nil = _ignored, {%{guard_position: {x, y}} = state, acc} -&gt;
        if x == :off_the_map or y == :off_the_map do
          {:halt, {state, acc}}
        else
          {:cont, mark_map(state, acc)}
        end
      end
    )
    |&gt; elem(1)
  end

  def solve() do
    File.read!("06/input.txt")
    |&gt; parse()
    |&gt; count_newly_traversed()
    |&gt; IO.puts()
  end
end

Day6.Part1.solve()
</code></pre>
<p>And part2:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">#!/usr/bin/env elixir

defmodule Day6.Part1 do
  @westward_facing_guard ~c"&lt;" |&gt; List.first()
  @eastward_facing_guard ~c"&gt;" |&gt; List.first()
  @northward_facing_guard ~c"^" |&gt; List.first()
  @southward_facing_guard ~c"v" |&gt; List.first()

  @guard_chars [
    @northward_facing_guard,
    @southward_facing_guard,
    @eastward_facing_guard,
    @westward_facing_guard
  ]
  @obstacle ~c"#" |&gt; List.first()
  @open_space ~c"." |&gt; List.first()

  defp parse(str) do
    [row | _tail] =
      rows =
      str
      |&gt; String.split("\n")
      |&gt; Enum.map(&amp;to_charlist/1)

    height = Enum.count(rows)
    length = Enum.count(row)

    {x, y} =
      guard_position =
      rows
      |&gt; Enum.with_index()
      |&gt; Enum.reduce_while(
        {nil, nil},
        fn {charlist, y_index}, {nil, nil} -&gt;
          x_index =
            charlist
            |&gt; Enum.find_index(fn c -&gt;
              c in @guard_chars
            end)

          if is_nil(x_index), do: {:cont, {nil, nil}}, else: {:halt, {x_index, y_index}}
        end
      )

    possible_spawn_points =
      for j &lt;- 0..(height - 1) do
        for i &lt;- 0..(length - 1) do
          if rows |&gt; Enum.at(j) |&gt; Enum.at(i) == @open_space, do: {i, j}, else: nil
        end
        |&gt; Enum.filter(fn i -&gt; not is_nil(i) end)
      end
      |&gt; List.flatten()

    orientation =
      rows
      |&gt; Enum.at(y)
      |&gt; Enum.at(x)

    %{
      initial_state: %{
        map: %{
          rows: rows,
          height: height,
          length: length
        },
        guard_position: guard_position,
        orientation: orientation
      },
      possible_spawn_points: possible_spawn_points
    }
  end

  @next_orientation %{
    @northward_facing_guard =&gt; @eastward_facing_guard,
    @eastward_facing_guard =&gt; @southward_facing_guard,
    @southward_facing_guard =&gt; @westward_facing_guard,
    @westward_facing_guard =&gt; @northward_facing_guard
  }

  defp store(
         seen,
         %{
           map: %{
             rows: rows,
             height: height,
             length: length
           },
           guard_position: {x, y} = guard_position,
           orientation: orientation
         } = state
       ) do
    row_above =
      if y == 0, do: :off_the_map, else: y - 1

    row_below =
      if y == height - 1, do: :off_the_map, else: y + 1

    column_before =
      if x == 0, do: :off_the_map, else: x - 1

    column_after =
      if x == length - 1, do: :off_the_map, else: x + 1

    seen =
      seen
      |&gt; MapSet.put({guard_position, orientation})

    {guard_position, orientation} =
      case orientation do
        @northward_facing_guard -&gt;
          next_is_obstacle? =
            if row_above == :off_the_map do
              false
            else
              rows |&gt; Enum.at(row_above) |&gt; Enum.at(x) == @obstacle
            end

          if next_is_obstacle? do
            {guard_position, @next_orientation[orientation]}
          else
            {{x, row_above}, orientation}
          end

        @southward_facing_guard -&gt;
          next_is_obstacle? =
            if row_below == :off_the_map do
              false
            else
              rows |&gt; Enum.at(row_below) |&gt; Enum.at(x) == @obstacle
            end

          if next_is_obstacle? do
            {guard_position, @next_orientation[orientation]}
          else
            {{x, row_below}, orientation}
          end

        @eastward_facing_guard -&gt;
          next_is_obstacle? =
            if column_after == :off_the_map do
              false
            else
              rows |&gt; Enum.at(y) |&gt; Enum.at(column_after) == @obstacle
            end

          if next_is_obstacle? do
            {guard_position, @next_orientation[orientation]}
          else
            {{column_after, y}, orientation}
          end

        @westward_facing_guard -&gt;
          next_is_obstacle? =
            if column_before == :off_the_map do
              false
            else
              rows |&gt; Enum.at(y) |&gt; Enum.at(column_before) == @obstacle
            end

          if next_is_obstacle? do
            {guard_position, @next_orientation[orientation]}
          else
            {{column_before, y}, orientation}
          end
      end

    {
      %{
        state
        | map: %{state.map | rows: rows},
          guard_position: guard_position,
          orientation: orientation
      },
      seen
    }
  end

  defp place_obstacle(%{map: %{rows: rows}} = state, x, y) do
    row =
      rows
      |&gt; Enum.at(y)

    obstructed_row =
      (row |&gt; Enum.take(x)) ++ [@obstacle] ++ (row |&gt; Enum.drop(x + 1))

    rows = (rows |&gt; Enum.take(y)) ++ [obstructed_row] ++ (rows |&gt; Enum.drop(y + 1))

    %{state | map: %{state.map | rows: rows}}
  end

  defp count_if_loop(state) do
    Stream.repeatedly(fn -&gt; nil end)
    |&gt; Enum.reduce_while(
      {state, MapSet.new()},
      fn nil = _ignored,
         {%{guard_position: {x, y} = guard_position, orientation: orientation} = state, seen} -&gt;
        cond do
          x == :off_the_map or y == :off_the_map -&gt;
            {:halt, 0}

          seen |&gt; MapSet.member?({guard_position, orientation}) -&gt;
            {:halt, 1}

          true -&gt;
            {:cont, seen |&gt; store(state)}
        end
      end
    )
  end

  defp count_creatable_loops(
         %{initial_state: initial_state, possible_spawn_points: possible_spawn_points}
       ) do
    possible_spawn_points
    |&gt; Enum.reduce(
      0,
      fn {x, y}, acc -&gt;
        acc +
          (initial_state
           |&gt; place_obstacle(x, y)
           |&gt; count_if_loop())
      end
    )
  end

  def solve() do
    File.read!("06/input.txt")
    |&gt; parse()
    |&gt; count_creatable_loops()
    |&gt; IO.puts()
  end
end

Day6.Part1.solve()
</code></pre>
<p>I may come back and try to improve perf here. Part 2 took about 1m25s to run on a <code>Intel® Core™ i7-10510U CPU @ 1.80GHz × 8</code></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="348908" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/advent-of-code-2024-day-6/67917/37">Post #36</a>
	                </div>
	            </div>
              <div id="likers-container-348908" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="348908"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #36"></div>
  </section>
</div>
    <div class="postbit" id="348926" data-post-id="348926">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="billylanchantin" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/billylanchantin/120/28428_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  billylanchantin
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Code golfing did not spark joy on this one so I took a crack at performance on Part 2 and got it down to the 300-400ms range:</p>
<pre><code class="lang-plaintext">Name                        ips        average  deviation         median         99th %
part_2_fast_async          2.65      377.78 ms     ±0.66%      377.81 ms      381.36 ms
part_2_fast                2.27      440.13 ms     ±1.12%      439.49 ms      452.15 ms
</code></pre>
<details>
<summary>Code</summary>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Aoc2024.Day06Fast do
  def part2(file) do
    file
    |&gt; main()
    |&gt; Enum.reduce(0, &amp;(&amp;2 + cycle_count(&amp;1)))
  end

  def part2_async(file) do
    file
    |&gt; main()
    |&gt; Task.async_stream(&amp;cycle_count/1, ordered: false)
    |&gt; Enum.reduce(0, fn {:ok, num}, sum -&gt; sum + num end)
  end

  def main(file) do
    rows =
      file
      |&gt; File.read!()
      |&gt; String.trim()
      |&gt; String.split("\n")
      |&gt; Enum.map(&amp;String.to_charlist/1)

    n = length(rows)

    grid =
      for {row, i} &lt;- Enum.with_index(rows, 1),
          {val, j} &lt;- Enum.with_index(row, 1),
          into: %{},
          do: {{i, j}, val}

    h =
      for i &lt;- 1..n, into: %{} do
        ranges =
          1..n
          |&gt; Enum.filter(&amp;(Map.get(grid, {i, &amp;1}) == ?#))
          |&gt; Enum.reduce([1..n], &amp;ranges_split(&amp;2, &amp;1))

        {i, ranges}
      end

    v =
      for j &lt;- 1..n, into: %{} do
        ranges =
          1..n
          |&gt; Enum.filter(&amp;(Map.get(grid, {&amp;1, j}) == ?#))
          |&gt; Enum.reduce([1..n], &amp;ranges_split(&amp;2, &amp;1))

        {j, ranges}
      end

    ranges = {h, v}
    {position, direction} = Enum.find(grid, fn {_, val} -&gt; val not in [?., ?#] end)

    path =
      steps(direction, position, ranges, grid)
      |&gt; Stream.chunk_every(2, 1, :discard)
      |&gt; Stream.flat_map(fn [{{a, b}, dir}, {{c, d}, _}] -&gt;
        for i &lt;- a..c, j &lt;- b..d, do: {{i, j}, dir}
      end)

    grid = Map.put(grid, position, ?.)

    :persistent_term.put(__MODULE__, {grid, ranges})

    path
    |&gt; Stream.uniq_by(&amp;elem(&amp;1, 0))
    |&gt; Stream.chunk_every(2, 1, :discard)
  end

  def cycle_count([{turning, _}, {turning, _}]), do: 0

  def cycle_count([{prev_position, prev_direction}, {{i, j} = position, _}]) do
    {grid, {h, v}} = :persistent_term.get(__MODULE__)
    grid = Map.put(grid, position, ?#)

    ranges =
      {Map.update!(h, i, &amp;ranges_split(&amp;1, j)), Map.update!(v, j, &amp;ranges_split(&amp;1, i))}

    steps(prev_direction, prev_position, ranges, grid)
    |&gt; Stream.drop(1)
    |&gt; Enum.reduce_while(MapSet.new(), fn x, visited -&gt;
      if MapSet.member?(visited, x) do
        {:halt, :cycle}
      else
        {:cont, MapSet.put(visited, x)}
      end
    end)
    |&gt; case do
      :cycle -&gt; 1
      _ -&gt; 0
    end
  end

  def turn_90(?^), do: ?&gt;
  def turn_90(?&gt;), do: ?v
  def turn_90(?v), do: ?&lt;
  def turn_90(?&lt;), do: ?^

  def turn_270(?^), do: ?&lt;
  def turn_270(?&gt;), do: ?^
  def turn_270(?v), do: ?&gt;
  def turn_270(?&lt;), do: ?v

  def step(?^, {row, col}, i), do: {row - i, col}
  def step(?&gt;, {row, col}, i), do: {row, col + i}
  def step(?v, {row, col}, i), do: {row + i, col}
  def step(?&lt;, {row, col}, i), do: {row, col - i}

  def find_range(d, {i, j}, {ranges, _}) when d in [?&lt;, ?&gt;] do
    Enum.find(Map.get(ranges, i), fn range -&gt; j in range end)
  end

  def find_range(d, {i, j}, {_, ranges}) when d in [?^, ?v] do
    Enum.find(Map.get(ranges, j), fn range -&gt; i in range end)
  end

  def steps(direction0, position0, ranges, grid) do
    Stream.unfold({true, position0, direction0}, fn {cont?, {i, j} = curr, direction} -&gt;
      if cont? do
        %{first: first, last: last} = find_range(direction, curr, ranges)

        next =
          case direction do
            ?^ -&gt; {first, j}
            ?&gt; -&gt; {i, last}
            ?v -&gt; {last, j}
            ?&lt; -&gt; {i, first}
          end

        prev_direction = turn_270(direction)

        {{curr, direction},
         {Map.has_key?(grid, step(prev_direction, curr, 1)), next, turn_90(direction)}}
      else
        nil
      end
    end)
  end

  def ranges_split(ranges, x) do
    {left, [range | right]} = Enum.split_while(ranges, &amp;(x not in &amp;1))
    left ++ range_split(range, x) ++ right
  end

  def range_split(a..c//1, a), do: [(a + 1)..c]
  def range_split(a..c//1, c), do: [a..(c - 1)]
  def range_split(a..c//1, b) when a &lt; b and b &lt; c, do: [a..(b - 1), (b + 1)..c]
end
</code></pre>
</details>
<p>The code is a mess so I’ll highlight the important part.</p>
<p>The main approach is to use ranges to traverse the path instead of individual grid cells. Here’s the range representation of the grid for input 1:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir"># Row ranges
%{1 =&gt; [1..4, 6..10], 2 =&gt; [1..9], ..., 10 =&gt; [1..6, 8..10]}

# Col ranges
%{1 =&gt; [1..8, 10..10], 2 =&gt; [1..6, 8..10], ..., 10 =&gt; [1..1, 3..10]}
</code></pre>
<p>Each row/col is represented as a list of ranges of <code>.</code>s with gaps for <code>#</code>s. This way, we don’t need to traverse each dot when walking left/right on a row or up/down on a col. We can simply jump to the <code>:first</code>/<code>:last</code> of the appropriate range. That cuts down significantly on how much work we need to do.</p>
<p>I did try a <code>Task.async</code> + <code>:persistent_term</code> approach but it yielded very little speed up. I haven’t tried other optimizations yet. Looks like <a class="mention" href="/u/bjorng" rel="nofollow">@bjorng</a>’s 200ish ms solution is the one to beat. I may have a look through to see if I can steal anything <img src="https://forum.elixirforum.com/images/emoji/apple/stuck_out_tongue.png?v=15" title=":stuck_out_tongue:" class="emoji" alt=":stuck_out_tongue:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="348926" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/advent-of-code-2024-day-6/67917/38">Post #37</a>
	                </div>
	            </div>
              <div id="likers-container-348926" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="348926"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #37"></div>
  </section>
</div>
    <div class="postbit" id="348941" data-post-id="348941">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="Adzz" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/Adzz/120/10743_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  Adzz
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Part one was much easier than I thought it was going to be at first. Mine runs in ~1.28ms on my machine.</p>
<p>I even tried tracking nodes as both a map and a list. Turns out it’s pretty similar, see benchmark at the end.</p>
<h2><a name="p-348941-part-1-1" class="anchor" href="#p-348941-part-1-1" aria-label="Heading link" rel="nofollow"></a>Part 1</h2>
<pre data-code-wrap="elixir"><code class="lang-elixir">  def day_6_1() do
    grid = File.read!("./day_6_input.txt")
    max_width = map_width(grid, 0)
    {x, y} = find_start(grid, {0, 0})
    walk(grid, :up, {x, y}, max_width, [{x, y}])
  end

  def map_width(&lt;&lt;@new_line, _::binary&gt;&gt;, count), do: count + 1
  def map_width(&lt;&lt;_::binary-size(1), rest::binary&gt;&gt;, count), do: map_width(rest, count + 1)

  @block "#"
  defp walk(grid, direction, coords, max_width, visited) do
    new_coords = coord_for_direction(direction, coords)

    case move(grid, new_coords, max_width) do
      :exited_map -&gt; length(Enum.uniq(visited))
      :block -&gt; walk(grid, turn_right(direction), coords, max_width, visited)
      :cont -&gt; walk(grid, direction, new_coords, max_width, [new_coords | visited])
    end
  end

  defp coord_for_direction(:up, {x, y}), do: {x, y - 1}
  defp coord_for_direction(:down, {x, y}), do: {x, y + 1}
  defp coord_for_direction(:left, {x, y}), do: {x - 1, y}
  defp coord_for_direction(:right, {x, y}), do: {x + 1, y}

  defp move(grid, {x, y}, max) do
    # The grid is square so these are the same (the input file gets saved with a new line at the end)
    if x &lt; 0 || y &lt; 0 || x &gt; max - 2 || y &gt; max - 2 do
      :exited_map
    else
      &lt;&lt;_::binary-size(x + max * y), rest::binary&gt;&gt; = grid
      case rest do
        &lt;&lt;@block, _::binary&gt;&gt; -&gt; :block
        _ -&gt; :cont
      end
    end
  end

  defp turn_right(:up), do: :right
  defp turn_right(:down), do: :left
  defp turn_right(:left), do: :up
  defp turn_right(:right), do: :down

  @caret "^"
  def find_start(&lt;&lt;@caret, _::binary&gt;&gt;, {x, y}), do: {x, y}
  def find_start(&lt;&lt;@new_line, rest::binary&gt;&gt;, {_, y}), do: find_start(rest, {0, y + 1})
  def find_start(&lt;&lt;_::binary-size(1), rest::binary&gt;&gt;, {x, y}), do: find_start(rest, {x + 1, y})
</code></pre>
<p>The benchmark below shows my approach with visited nodes as a map and visited nodes as a list.<br>
The map version did this instead:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">  def day_6_1_map() do
    grid = File.read!("./day_6_input.txt")
    max_width = map_width(grid, 0)
    {x, y} = find_start(grid, {0, 0})
    walk_map(grid, :up, {x, y}, max_width, %{ {x, y} =&gt; 1})
  end
  defp walk_map(grid, direction, coords, max_width, visited) do
    new_coords = coord_for_direction(direction, coords)

    case move(grid, new_coords, max_width) do
      :exited_map -&gt; map_size(visited)
      :block -&gt; walk_map(grid, turn_right(direction), coords, max_width, visited)
      :cont -&gt; walk_map(grid, direction, new_coords, max_width, Map.put_new(visited, new_coords, 1))
    end
  end

</code></pre>
<pre data-code-wrap="elixir"><code class="lang-elixir">Benchmarking Day 6 1 LIST ...
Benchmarking Day 6 1 MAP ...
Calculating statistics...
Formatting results...

Name                   ips        average  deviation         median         99th %
Day 6 1 MAP         774.25        1.29 ms    ±16.84%        1.26 ms        1.70 ms
Day 6 1 LIST        763.53        1.31 ms     ±7.03%        1.28 ms        1.56 ms

Comparison:
Day 6 1 MAP         774.25
Day 6 1 LIST        763.53 - 1.01x slower +0.0181 ms

Memory usage statistics:

Name            Memory usage
Day 6 1 MAP          2.26 MB
Day 6 1 LIST         2.41 MB - 1.07x memory usage +0.157 MB

**All measurements for memory usage were the same**

Reduction count statistics:

Name         Reduction count
Day 6 1 MAP          63.96 K
Day 6 1 LIST         74.58 K - 1.17x reduction count +10.63 K

**All measurements for reduction count were the same**
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="348941" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/advent-of-code-2024-day-6/67917/39">Post #38</a>
	                </div>
	            </div>
              <div id="likers-container-348941" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="348941"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #38"></div>
  </section>
</div>
    <div class="postbit" id="349188" data-post-id="349188">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="MikeLindner" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  MikeLindner
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I ended up with a bunch of duplicated code between part 1 and part 2. The biggest challenge was figuring out what I was doing wrong that was making part 2 really slow. It turned out to be how I was checking for loops by creating a list of the visited nodes and searching it at each node. Changing that to a MapSet got me to a solution.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Aoc2024.Day6 do
  @moduledoc false

  @guard_up "^"
  @guard_right "&gt;"
  @guard_down "v"
  @guard_left "&lt;"
  @obstruction "#"
  @visited "X"

  defp get_input(file) do
    File.read!(file)
    |&gt; String.split("\n")
    |&gt; Enum.filter(fn line_data -&gt; line_data != "" end)
    |&gt; Enum.map(&amp;String.split(&amp;1, "", trim: true))
  end

  defp visit(grid, {x, y}) do
    List.update_at(grid, y, fn row -&gt; List.replace_at(row, x, @visited) end)
  end

  defp patrol(map, obstructions) do
    width = length(List.first(map))

    # Find the guard, then patrol.
    indexed =
      map
      |&gt; List.flatten()
      |&gt; Enum.with_index()

    {guard, i} =
      List.keyfind(indexed, @guard_up, 0) ||
        List.keyfind(indexed, @guard_down, 0) ||
        List.keyfind(indexed, @guard_left, 0) ||
        List.keyfind(indexed, @guard_right, 0)

    facing =
      case guard do
        @guard_up -&gt; :up
        @guard_down -&gt; :down
        @guard_left -&gt; :left
        @guard_right -&gt; :right
      end

    start = {Integer.mod(i, width), Integer.floor_div(i, width), facing}
    bounds = {length(List.first(map)) - 1, length(map) - 1}

    patrol(map, obstructions, bounds, start)
  end

  defp patrol(map, obstructions, bounds = {last_x, last_y}, loc = {x, y, _facing}, route \\ []) do
    map = visit(map, {x, y})
    next = {next_x, next_y, _next_facing} = look(obstructions, loc, bounds)

    route = route ++ [loc]

    if next_x &lt; 0 or next_y &lt; 0 or next_x &gt; last_x or next_y &gt; last_y do
      {map, route}
    else
      patrol(map, obstructions, bounds, next, route)
    end
  end

  defp walk(obstructions, loc, bounds = {last_x, last_y}, visited \\ MapSet.new()) do
    next = {next_x, next_y, _next_facing} = look(obstructions, loc, bounds)

    cond do
      next_x &lt; 0 or next_y &lt; 0 or next_x &gt; last_x or next_y &gt; last_y -&gt;
        0

      MapSet.member?(visited, loc) -&gt;
        1

      true -&gt;
        walk(obstructions, next, bounds, MapSet.put(visited, loc))
    end
  end

  defp look(obstructions, {x, y, :up}, {last_x, last_y}) do
    ahead = {x, y - 1}

    if y &gt; 0 and MapSet.member?(obstructions, ahead) do
      look(obstructions, {x, y, :right}, {last_x, last_y})
    else
      Tuple.append(ahead, :up)
    end
  end

  defp look(obstructions, {x, y, :down}, {last_x, last_y}) do
    ahead = {x, y + 1}

    if y &lt; last_y and MapSet.member?(obstructions, ahead) do
      look(obstructions, {x, y, :left}, {last_x, last_y})
    else
      Tuple.append(ahead, :down)
    end
  end

  defp look(obstructions, {x, y, :left}, {last_x, last_y}) do
    ahead = {x - 1, y}

    if x &gt; 0 and MapSet.member?(obstructions, ahead) do
      look(obstructions, {x, y, :up}, {last_x, last_y})
    else
      Tuple.append(ahead, :left)
    end
  end

  defp look(obstructions, {x, y, :right}, {last_x, last_y}) do
    ahead = {x + 1, y}

    if x &lt; last_x and MapSet.member?(obstructions, ahead) do
      look(obstructions, {x, y, :down}, {last_x, last_y})
    else
      Tuple.append(ahead, :right)
    end
  end

  def part1(file) do
    map = get_input(file)

    width = length(List.first(map))

    obstructions =
      map
      |&gt; List.flatten()
      |&gt; Enum.with_index()
      |&gt; Enum.reduce([], fn {space, i}, acc -&gt;
        if space == @obstruction do
          [{Integer.mod(i, width), Integer.floor_div(i, width)} | acc]
        else
          acc
        end
      end)
      |&gt; MapSet.new()

    {map, _} = patrol(map, obstructions)

    map
    |&gt; List.flatten()
    |&gt; Enum.frequencies()
    |&gt; Map.get(@visited)
  end

  def part2(file) do
    map = get_input(file)

    last_x = length(List.first(map)) - 1
    last_y = length(map) - 1

    width = length(List.first(map))

    indexed =
      map
      |&gt; List.flatten()
      |&gt; Enum.with_index()

    {guard, guard_idx} =
      List.keyfind(indexed, @guard_up, 0) ||
        List.keyfind(indexed, @guard_down, 0) ||
        List.keyfind(indexed, @guard_left, 0) ||
        List.keyfind(indexed, @guard_right, 0)

    facing =
      case guard do
        @guard_up -&gt; :up
        @guard_down -&gt; :down
        @guard_left -&gt; :left
        @guard_right -&gt; :right
      end

    start = {Integer.mod(guard_idx, width), Integer.floor_div(guard_idx, width), facing}

    obstructions =
      indexed
      |&gt; Enum.reduce([], fn {space, i}, acc -&gt;
        if space == @obstruction do
          [{Integer.mod(i, width), Integer.floor_div(i, width)} | acc]
        else
          acc
        end
      end)
      |&gt; MapSet.new()

    {_, route} = patrol(map, obstructions)

    visited =
      route
      |&gt; Enum.map(fn {x, y, _} -&gt; {x, y} end)
      |&gt; Enum.filter(fn loc -&gt; loc != start end)
      |&gt; Enum.uniq()

    # Put an obstruction at each visited location and see if it loops.
    visited
    |&gt; Enum.map(fn loc -&gt;
      MapSet.put(obstructions, loc)
      |&gt; walk(start, {last_x, last_y})
    end)
    |&gt; Enum.sum()
  end
end
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="349188" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/advent-of-code-2024-day-6/67917/40">Post #39</a>
	                </div>
	            </div>
              <div id="likers-container-349188" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="349188"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #39"></div>
  </section>
</div>
    <div class="postbit" id="349923" data-post-id="349923">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="al2o3cr" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/al2o3cr/120/3457_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  al2o3cr
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p><code>:ets</code>’s <code>:ordered_set</code> was really useful here.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Guarding do
  def read(filename, max_rows, max_cols) do
    :ets.new(__MODULE__, [:ordered_set, :named_table])
    :ets.insert(__MODULE__, {:max_rows, max_rows})
    :ets.insert(__MODULE__, {:max_cols, max_cols})

    File.stream!(filename)
    |&gt; Stream.map(&amp;String.trim/1)
    |&gt; Stream.with_index()
    |&gt; Stream.flat_map(fn {row, r_idx} -&gt; parse_row(row, r_idx) end)
    |&gt; Enum.each(fn
      {:wall, {row, col}} -&gt;
        :ets.insert(__MODULE__, {{:by_row, row, col}, true})
        :ets.insert(__MODULE__, {{:by_col, col, row}, true})

      {:start, pos} -&gt;
        :ets.insert(__MODULE__, {:start, pos})
    end)
  end

  defp parse_row(row, r_idx) do
    row
    |&gt; String.codepoints()
    |&gt; Enum.with_index()
    |&gt; Enum.flat_map(fn
      {".", _} -&gt; []
      {"#", c_idx} -&gt; [{:wall, {r_idx, c_idx}}]
      {"^", c_idx} -&gt; [{:start, {r_idx, c_idx}}]
    end)
  end

  def next_direction(:up), do: :right
  def next_direction(:right), do: :down
  def next_direction(:down), do: :left
  def next_direction(:left), do: :up

  def next_in_direction(:out_of_bounds), do: nil

  def next_in_direction({{row, col}, :right}) do
    case :ets.next(__MODULE__, {:by_row, row, col}) do
      {:by_row, ^row, next_col} -&gt;
        {Enum.map(col..(next_col-1), &amp;{row, &amp;1}), {{row, next_col - 1}, :down}}

      _ -&gt;
        {Enum.map(col..max_cols(), &amp;{row, &amp;1}), :out_of_bounds}
    end
  end

  def next_in_direction({{row, col}, :left}) do
    case :ets.prev(__MODULE__, {:by_row, row, col}) do
      {:by_row, ^row, prev_col} -&gt;
        {Enum.map((prev_col+1)..col, &amp;{row, &amp;1}), {{row, prev_col + 1}, :up}}

      _ -&gt;
        {Enum.map(0..col, &amp;{row, &amp;1}), :out_of_bounds}
    end
  end

  def next_in_direction({{row, col}, :up}) do
    case :ets.prev(__MODULE__, {:by_col, col, row}) do
      {:by_col, ^col, prev_row} -&gt;
        {Enum.map((prev_row+1)..row, &amp;{&amp;1, col}), {{prev_row + 1, col}, :right}}

      _ -&gt;
        {Enum.map(0..row, &amp;{&amp;1, col}), :out_of_bounds}
    end
  end

  def next_in_direction({{row, col}, :down}) do
    case :ets.next(__MODULE__, {:by_col, col, row}) do
      {:by_col, ^col, next_row} -&gt;
        {Enum.map(row..(next_row-1), &amp;{&amp;1, col}), {{next_row - 1, col}, :left}}

      _ -&gt;
        {Enum.map(row..max_rows(), &amp;{&amp;1, col}), :out_of_bounds}
    end
  end

  def start do
    pos = :ets.lookup_element(__MODULE__, :start, 2)
    {pos, :up}
  end

  defp max_rows do
    :ets.lookup_element(__MODULE__, :max_rows, 2)
  end

  defp max_cols do
    :ets.lookup_element(__MODULE__, :max_cols, 2)
  end
end

Guarding.read("input.txt", 129, 129)

Guarding.start()
|&gt; Stream.unfold(&amp;Guarding.next_in_direction/1)
|&gt; Enum.to_list()
|&gt; List.flatten()
|&gt; Enum.uniq()
|&gt; length()
|&gt; IO.inspect()
</code></pre>
<p>The idea is to keep two indexes in ETS:</p>
<ul>
<li><code>:by_row</code> - the keys here are sorted so that the “next” and “previous” obstacle in a row are accessible with <code>:ets.next</code> and <code>:ets.prev</code></li>
<li><code>:by_col</code> - same thing, but for a specific column</li>
</ul>
<p>This works because of how tuples sort. <code>{:by_row, 5, 0}</code> &lt; <code>{:by_row, 5, 4}</code> &lt; <code>{:by_row, 5, 10}</code></p>
<p>One case to watch for is when the search “hits the edge” - <code>{:by_row, 4, 4}</code> &lt; <code>{:by_row, 5, 3}</code>, which means the search for the “previous” <code>:by_row</code> for <code>{5,3}</code> hit the edge. The pinned value in each <code>case</code> causes that situation (and <code>:"$end_of_table"</code>) to not match.</p>
<p>Part 2 is trickier - being at <code>{3,4}</code> and moving up is distinct from being at <code>{3,4}</code> and moving left, so the output of <code>next_in_direction</code> becomes <code>{{row, col}, dir}</code> tuples.</p>
<p>It also uses a minor optimization and only tries putting obstacles at places that the unmodified route visits, which cuts down the number of possible locations by about 2/3rds for my input.</p>
<p>Part 2:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Guarding do
  def read(filename, max_rows, max_cols) do
    :ets.new(__MODULE__, [:ordered_set, :named_table])
    :ets.insert(__MODULE__, {:max_rows, max_rows})
    :ets.insert(__MODULE__, {:max_cols, max_cols})

    File.stream!(filename)
    |&gt; Stream.map(&amp;String.trim/1)
    |&gt; Stream.with_index()
    |&gt; Stream.flat_map(fn {row, r_idx} -&gt; parse_row(row, r_idx) end)
    |&gt; Enum.each(fn
      {:wall, {row, col}} -&gt;
        :ets.insert(__MODULE__, {{:by_row, row, col}, true})
        :ets.insert(__MODULE__, {{:by_col, col, row}, true})

      {:start, pos} -&gt;
        :ets.insert(__MODULE__, {:start, pos})
    end)
  end

  defp parse_row(row, r_idx) do
    row
    |&gt; String.codepoints()
    |&gt; Enum.with_index()
    |&gt; Enum.flat_map(fn
      {".", _} -&gt; []
      {"#", c_idx} -&gt; [{:wall, {r_idx, c_idx}}]
      {"^", c_idx} -&gt; [{:start, {r_idx, c_idx}}]
    end)
  end

  def next_in_direction(:out_of_bounds), do: nil

  def next_in_direction({{row, col}, :right}) do
    case :ets.next(__MODULE__, {:by_row, row, col}) do
      {:by_row, ^row, next_col} -&gt;
        {Enum.map(col..(next_col-1), &amp;{row, &amp;1, :right}), {{row, next_col - 1}, :down}}

      _ -&gt;
        {Enum.map(col..max_cols(), &amp;{row, &amp;1, :right}), :out_of_bounds}
    end
  end

  def next_in_direction({{row, col}, :left}) do
    case :ets.prev(__MODULE__, {:by_row, row, col}) do
      {:by_row, ^row, prev_col} -&gt;
        {Enum.map((prev_col+1)..col, &amp;{row, &amp;1, :left}), {{row, prev_col + 1}, :up}}

      _ -&gt;
        {Enum.map(0..col, &amp;{row, &amp;1, :left}), :out_of_bounds}
    end
  end

  def next_in_direction({{row, col}, :up}) do
    case :ets.prev(__MODULE__, {:by_col, col, row}) do
      {:by_col, ^col, prev_row} -&gt;
        {Enum.map((prev_row+1)..row, &amp;{&amp;1, col, :up}), {{prev_row + 1, col}, :right}}

      _ -&gt;
        {Enum.map(0..row, &amp;{&amp;1, col, :up}), :out_of_bounds}
    end
  end

  def next_in_direction({{row, col}, :down}) do
    case :ets.next(__MODULE__, {:by_col, col, row}) do
      {:by_col, ^col, next_row} -&gt;
        {Enum.map(row..(next_row-1), &amp;{&amp;1, col, :down}), {{next_row - 1, col}, :left}}

      _ -&gt;
        {Enum.map(row..max_rows(), &amp;{&amp;1, col, :down}), :out_of_bounds}
    end
  end

  def start do
    pos = :ets.lookup_element(__MODULE__, :start, 2)
    {pos, :up}
  end

  defp max_rows do
    :ets.lookup_element(__MODULE__, :max_rows, 2)
  end

  defp max_cols do
    :ets.lookup_element(__MODULE__, :max_cols, 2)
  end

  def loop_with?({new_row, new_col}) do
    if :ets.member(__MODULE__, {:by_row, new_row, new_col}) do
      false
    else
      :ets.insert(__MODULE__, {{:by_row, new_row, new_col}, true})
      :ets.insert(__MODULE__, {{:by_col, new_col, new_row}, true})

      result = loop?(start())

      :ets.delete(__MODULE__, {:by_row, new_row, new_col})
      :ets.delete(__MODULE__, {:by_col, new_col, new_row})

      result
    end
  end

  def loop?(start) do
    start
    |&gt; Stream.unfold(&amp;next_in_direction/1)
    |&gt; Stream.concat()
    |&gt; Stream.transform({MapSet.new(), false}, fn 
      pos, {acc, false} -&gt;
        if MapSet.member?(acc, pos) do
          {[true], {acc, true}}
        else
          {[false], {MapSet.put(acc, pos), false}}
        end

      _, {acc, true} -&gt;
        {:halt, {acc, true}}
    end)
    |&gt; Stream.take(-1)
    |&gt; Enum.to_list()
    |&gt; hd()
  end
end

# Guarding.read("example.txt", 9, 9)
Guarding.read("input.txt", 129, 129)

{start_pos, _} = start = Guarding.start()

existing_visited =
  start
  |&gt; Stream.unfold(&amp;Guarding.next_in_direction/1)
  |&gt; Enum.to_list()
  |&gt; List.flatten()
  |&gt; Enum.map(fn {r, c, _dir} -&gt; {r, c} end)
  |&gt; Enum.uniq()
  |&gt; then(&amp; &amp;1 -- [start_pos])

existing_visited
|&gt; Enum.filter(&amp;Guarding.loop_with?/1)
|&gt; length()
|&gt; IO.inspect()

</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="349923" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/advent-of-code-2024-day-6/67917/41">Post #40</a>
	                </div>
	            </div>
              <div id="likers-container-349923" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="349923"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #40"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <a class="load-more-button" data-turbo-stream="true" href="/topics/67917/load_more?page=5">Load more posts (2 remaining)</a>
</div></template></turbo-stream>