Powered by AppSignal & Oban Pro

🎄 Year 2023 🔔 Day 14

elixir/notebooks/2023/day14.livemd

🎄 Year 2023 🔔 Day 14

Setup

map =
  File.read!("#{__DIR__}/../../../inputs/2023/day14.txt")
  |> String.split("\n", trim: true)
  |> Enum.map(&String.split(&1, "", trim: true))
  |> Enum.with_index(fn line, y ->
    Enum.with_index(line, fn char, x ->
      {{x, y}, char}
    end)
  end)
  |> Enum.concat()
  |> Map.new()

{{max_x, max_y}, _} = Enum.max_by(map, fn {{x, y}, _} -> x + y end)
defmodule Helper do
  def sum_coords({x1, y1}, {x2, y2}), do: {x1 + x2, y1 + y2}

  # north, then west, then south, then east
  def move_north(map, max_x, max_y) do
    coords = for y <- 0..max_y, x <- 0..max_x, do: {x, y}
    move_coord = {0, -1}
    move_stones(map, coords, move_coord)
  end

  def move_west(map, max_x, max_y) do
    coords = for x <- 0..max_x, y <- 0..max_y, do: {x, y}
    move_coord = {-1, 0}
    move_stones(map, coords, move_coord)
  end

  def move_south(map, max_x, max_y) do
    coords = for y <- max_y..0, x <- 0..max_x, do: {x, y}
    move_coord = {0, 1}
    move_stones(map, coords, move_coord)
  end

  def move_east(map, max_x, max_y) do
    coords = for x <- max_x..0, y <- 0..max_y, do: {x, y}
    move_coord = {1, 0}
    move_stones(map, coords, move_coord)
  end

  defp move_stones(map, coords, move_coord) do
    coords
    |> Enum.filter(fn coord -> Map.fetch!(map, coord) == "O" end)
    |> Enum.reduce(map, fn tm_coord, map ->
      new_coord =
        Stream.unfold(tm_coord, fn coords ->
          cond do
            tm_coord == coords ->
              {coords, sum_coords(coords, move_coord)}

            Map.get(map, coords) == "." ->
              {coords, sum_coords(coords, move_coord)}

            true ->
              nil
          end
        end)
        |> Enum.to_list()
        |> List.last(tm_coord)

      map
      |> Map.update!(tm_coord, fn _ -> "." end)
      |> Map.update!(new_coord, fn _ -> "O" end)
    end)
  end
end

Part 1

import Helper

map
|> move_north(max_x, max_y)
|> Enum.filter(fn {_coord, char} -> char == "O" end)
|> Enum.map(fn {{_x, y}, _} ->
  max_y + 1 - y
end)
|> Enum.sum()

Part 2

import Helper

{map, stored_maps, first_index, second_index} =
  Stream.iterate(1, &(&1 + 1))
  |> Enum.reduce_while({%{}, map}, fn i, {stored_maps, map} ->
    new_map =
      case rem(i, 4) do
        1 -> move_north(map, max_x, max_y)
        2 -> move_west(map, max_x, max_y)
        3 -> move_south(map, max_x, max_y)
        0 -> move_east(map, max_x, max_y)
      end

    if Map.has_key?(stored_maps, new_map) do
      prev_index = Map.fetch!(stored_maps, new_map)
      {:halt, {new_map, stored_maps, prev_index, i}}
    else
      new_stored_maps = Map.put_new(stored_maps, new_map, i)
      {:cont, {new_stored_maps, new_map}}
    end
  end)

loop_length = second_index - first_index
steps_after_loop_starts = rem(1_000_000_000 - first_index, loop_length - 1)

final_map =
  Enum.find_value(stored_maps, fn {map, i} ->
    if i == first_index + steps_after_loop_starts, do: map, else: nil
  end)

final_map
|> Enum.filter(fn {_coord, char} -> char == "O" end)
|> Enum.map(fn {{_x, y}, _} ->
  max_y + 1 - y
end)
|> Enum.sum()