Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day14

2023/day14.livemd

Day14

Mix.install([
  {:kino, "~> 0.11.0"}
])

Section

input = Kino.Input.textarea("Input", monospace: true)
defmodule Reflector do
  def tilt(map, direction) do
    map
    |> sort(direction)
    |> shift(direction)
  end

  def cycle(map) do
    map
    |> tilt(N)
    |> tilt(W)
    |> tilt(S)
    |> tilt(E)
  end

  defp shift(map, dir) do
    Enum.reduce(map, [], fn
      {_, "#"} = rock, rocks ->
        [rock | rocks]

      {{x, _y}, "O"}, [] when dir == N ->
        [{{x, 0}, "O"}]

      {{_x, y}, "O"}, [] when dir == W ->
        [{{0, y}, "O"}]

      {{x, _y}, "O"}, [] when dir == S ->
        [{{x, size(map, dir)}, "O"}]

      {{_x, y}, "O"}, [] when dir == E ->
        [{{size(map, dir), y}, "O"}]

      {{x, _y}, "O"}, [{{x, y}, _} = prev | rocks] when dir == N ->
        [{{x, y + 1}, "O"}, prev | rocks]

      {{x, _y}, "O"}, [{{x, y}, _} = prev | rocks] when dir == S ->
        [{{x, y - 1}, "O"}, prev | rocks]

      {{_x, y}, "O"}, [{{x, y}, _} = prev | rocks] when dir == E ->
        [{{x - 1, y}, "O"}, prev | rocks]

      {{_x, y}, "O"}, [{{x, y}, _} = prev | rocks] when dir == W ->
        [{{x + 1, y}, "O"}, prev | rocks]

      {{x, _y}, "O"}, [prev | rocks] when dir == N ->
        [{{x, 0}, "O"}, prev | rocks]

      {{_x, y}, "O"}, [prev | rocks] when dir == W ->
        [{{0, y}, "O"}, prev | rocks]

      {{x, _y}, "O"}, [prev | rocks] when dir == S ->
        [{{x, size(map, dir)}, "O"}, prev | rocks]

      {{_x, y}, "O"}, [prev | rocks] when dir == E ->
        [{{size(map, dir), y}, "O"}, prev | rocks]
    end)
  end

  defp size(map, S), do: Enum.max_by(map, fn {{_, y}, _} -> y end) |> elem(0) |> elem(1)
  defp size(map, E), do: Enum.max_by(map, fn {{x, _}, _} -> x end) |> elem(0) |> elem(0)

  defp sort(map, direction) when direction in [N, S] do
    Enum.sort(map, dir(direction))
  end

  defp sort(map, direction) when direction in [E, W] do
    Enum.sort_by(map, fn {{x, y}, _} -> {y, x} end, dir(direction))
  end

  defp dir(direction) when direction in [N, W], do: :asc
  defp dir(direction) when direction in [E, S], do: :desc

  def weight(map, N, y_max) do
    map
    |> Enum.reject(fn {_, v} -> v == "#" end)
    |> Enum.map(&elem(&1, 0))
    |> Enum.map(&elem(&1, 1))
    |> Enum.map(&(y_max - &1))
    |> Enum.sum()
  end

  def print(map) do
    tap(map, fn map ->
      map = Map.new(map)

      IO.puts(
        for y <- 0..9 do
          for(x <- 0..9, into: "", do: Map.get(map, {x, y}, ".")) <> "\n"
        end
      )
    end)
  end
end

y_max =
  input
  |> Kino.Input.read()
  |> String.split("\n")
  |> Enum.count()

map =
  input
  |> Kino.Input.read()
  |> String.split("\n")
  |> Enum.with_index()
  |> Enum.flat_map(fn {xs, y} ->
    xs
    |> String.split("", trim: true)
    |> Enum.with_index()
    |> Enum.map(fn {v, x} -> {{x, y}, v} end)
  end)
  |> Enum.reject(&amp;match?({_, "."}, &amp;1))
map
|> Reflector.tilt(N)
|> Reflector.weight(N, y_max)
Stream.interval(1)
|> Stream.take(2000)
|> Enum.reduce_while({map, []}, fn _, {map, maps} ->
  map
  |> Reflector.cycle()
  |> Enum.sort()
  |> then(fn map ->
    if map in maps do
      {:halt, [map | maps]}
    else
      {:cont, {map, [map | maps]}}
    end
  end)
end)
|> then(fn [map | maps] ->
  maps
  |> Enum.reverse()
  |> Enum.split_while(&amp;(&amp;1 != map))
  |> then(fn {head, tail} ->
    tail
    |> Enum.at(rem(1_000_000_000 - Enum.count(head), Enum.count(tail)) - 1)
    |> Reflector.weight(N, y_max)
  end)
end)