I got some sleep yesterday, but that means I’m playing catch-up.
Here’s pt1 for me:
#!/usr/bin/env elixir defmodule Day6.Part1 do @westward_facing_guard ~c"<" |> List.first() @eastward_facing_guard ~c">" |> List.first() @northward_facing_guard ~c"^" |> List.first() @southward_facing_guard ~c"v" |> List.first() @guard_chars [ @northward_facing_guard, @southward_facing_guard, @eastward_facing_guard, @westward_facing_guard ] @obstacle ~c"#" |> List.first() @open_space ~c"." |> List.first() @seen ~c"X" |> List.first() defp parse(str) do [row | _tail] = rows = str |> String.split("\n") |> Enum.map(&to_charlist/1) height = Enum.count(rows) length = Enum.count(row) {x, y} = guard_position = rows |> Enum.with_index() |> Enum.reduce_while( {nil, nil}, fn {charlist, y_index}, {nil, nil} -> x_index = charlist |> Enum.find_index(fn c -> c in @guard_chars end) if is_nil(x_index), do: {:cont, {nil, nil}}, else: {:halt, {x_index, y_index}} end ) guard_row = rows |> Enum.at(y) orientation = guard_row |> Enum.at(x) sanitized_guard_row = guard_row |> Enum.map(fn c -> if c in @guard_chars, do: @open_space, else: c end) rows = (rows |> Enum.take(y)) ++ [sanitized_guard_row] ++ (rows |> Enum.drop(y + 1)) %{ map: %{ rows: rows, height: height, length: length }, guard_position: guard_position, orientation: orientation } end @next_orientation %{ @northward_facing_guard => @eastward_facing_guard, @eastward_facing_guard => @southward_facing_guard, @southward_facing_guard => @westward_facing_guard, @westward_facing_guard => @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 |> Enum.at(y) current_value = current_row |> Enum.at(x) marked_row = (current_row |> Enum.take(x)) ++ [@seen] ++ (current_row |> Enum.drop(x + 1)) rows = (rows |> Enum.take(y)) ++ [marked_row] ++ (rows |> Enum.drop(y + 1)) {guard_position, orientation} = case orientation do @northward_facing_guard -> next_is_obstacle? = if row_above == :off_the_map do false else rows |> Enum.at(row_above) |> Enum.at(x) == @obstacle end if next_is_obstacle? do {guard_position, @next_orientation[orientation]} else {{x, row_above}, orientation} end @southward_facing_guard -> next_is_obstacle? = if row_below == :off_the_map do false else rows |> Enum.at(row_below) |> Enum.at(x) == @obstacle end if next_is_obstacle? do {guard_position, @next_orientation[orientation]} else {{x, row_below}, orientation} end @eastward_facing_guard -> next_is_obstacle? = if column_after == :off_the_map do false else rows |> Enum.at(y) |> 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 -> next_is_obstacle? = if column_before == :off_the_map do false else rows |> Enum.at(y) |> 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 -> nil end) |> Enum.reduce_while( {state, 0}, fn nil = _ignored, {%{guard_position: {x, y}} = state, acc} -> if x == :off_the_map or y == :off_the_map do {:halt, {state, acc}} else {:cont, mark_map(state, acc)} end end ) |> elem(1) end def solve() do File.read!("06/input.txt") |> parse() |> count_newly_traversed() |> IO.puts() end end Day6.Part1.solve()
And part2:
#!/usr/bin/env elixir defmodule Day6.Part1 do @westward_facing_guard ~c"<" |> List.first() @eastward_facing_guard ~c">" |> List.first() @northward_facing_guard ~c"^" |> List.first() @southward_facing_guard ~c"v" |> List.first() @guard_chars [ @northward_facing_guard, @southward_facing_guard, @eastward_facing_guard, @westward_facing_guard ] @obstacle ~c"#" |> List.first() @open_space ~c"." |> List.first() defp parse(str) do [row | _tail] = rows = str |> String.split("\n") |> Enum.map(&to_charlist/1) height = Enum.count(rows) length = Enum.count(row) {x, y} = guard_position = rows |> Enum.with_index() |> Enum.reduce_while( {nil, nil}, fn {charlist, y_index}, {nil, nil} -> x_index = charlist |> Enum.find_index(fn c -> 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 <- 0..(height - 1) do for i <- 0..(length - 1) do if rows |> Enum.at(j) |> Enum.at(i) == @open_space, do: {i, j}, else: nil end |> Enum.filter(fn i -> not is_nil(i) end) end |> List.flatten() orientation = rows |> Enum.at(y) |> 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 => @eastward_facing_guard, @eastward_facing_guard => @southward_facing_guard, @southward_facing_guard => @westward_facing_guard, @westward_facing_guard => @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 |> MapSet.put({guard_position, orientation}) {guard_position, orientation} = case orientation do @northward_facing_guard -> next_is_obstacle? = if row_above == :off_the_map do false else rows |> Enum.at(row_above) |> Enum.at(x) == @obstacle end if next_is_obstacle? do {guard_position, @next_orientation[orientation]} else {{x, row_above}, orientation} end @southward_facing_guard -> next_is_obstacle? = if row_below == :off_the_map do false else rows |> Enum.at(row_below) |> Enum.at(x) == @obstacle end if next_is_obstacle? do {guard_position, @next_orientation[orientation]} else {{x, row_below}, orientation} end @eastward_facing_guard -> next_is_obstacle? = if column_after == :off_the_map do false else rows |> Enum.at(y) |> 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 -> next_is_obstacle? = if column_before == :off_the_map do false else rows |> Enum.at(y) |> 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 |> Enum.at(y) obstructed_row = (row |> Enum.take(x)) ++ [@obstacle] ++ (row |> Enum.drop(x + 1)) rows = (rows |> Enum.take(y)) ++ [obstructed_row] ++ (rows |> Enum.drop(y + 1)) %{state | map: %{state.map | rows: rows}} end defp count_if_loop(state) do Stream.repeatedly(fn -> nil end) |> Enum.reduce_while( {state, MapSet.new()}, fn nil = _ignored, {%{guard_position: {x, y} = guard_position, orientation: orientation} = state, seen} -> cond do x == :off_the_map or y == :off_the_map -> {:halt, 0} seen |> MapSet.member?({guard_position, orientation}) -> {:halt, 1} true -> {:cont, seen |> store(state)} end end ) end defp count_creatable_loops( %{initial_state: initial_state, possible_spawn_points: possible_spawn_points} ) do possible_spawn_points |> Enum.reduce( 0, fn {x, y}, acc -> acc + (initial_state |> place_obstacle(x, y) |> count_if_loop()) end ) end def solve() do File.read!("06/input.txt") |> parse() |> count_creatable_loops() |> IO.puts() end end Day6.Part1.solve()
I may come back and try to improve perf here. Part 2 took about 1m25s to run on a Intel® Core™ i7-10510U CPU @ 1.80GHz × 8