Advent of Code 2023 - Day 14

Hello, coding maestros! Ukrainian Erlanger at your service :metal:, and it’s a thrilling Thursday. Today marks Day 14 of our exhilarating journey through Advent of Code, presenting a fresh puzzle for you to conquer. Dive into the challenge, flex those coding muscles, and, of course, share your ingenious solutions in the comments below. Let’s keep the coding camaraderie strong and turn this Thursday into a day of triumph! Happy coding, everyone! :blush:

3 Likes

I may have made that more complicated than it needed to be with the way I represented the grid. It was kind of slick for part 1 though.

2 Likes

For part 2 found cycle manually first, then implemented search for it :sweat_smile:

defmodule Aoc.Fourteen do
  def parse(string) do
    string
    |> String.split("\n", trim: true)
    |> Enum.map(fn line ->
      line
      |> String.graphemes()
    end)
    |> load()
  end

  def load(data) do
    for {line, y} <- Enum.with_index(data, 1), {i, x} <- Enum.with_index(line, 1), into: %{} do
      {{y, x}, i}
    end
  end

  def part_one(input) do
    input
    |> tilt()
  end

  def part_two(input) do
    input
    |> cycle(1_000_000_000)
  end

  defp tilt(map) do
    {y_max, x_max} = maxs = map |> Map.keys() |> Enum.max()

    for y <- 1..y_max, x <- 1..x_max do
      {y, x}
    end
    |> Enum.reduce(filter_map(map), &tilt(&1, maxs, :north, &2))
    |> north_weight(y_max)
  end

  defp filter_map(map) do
    map |> Map.filter(&(elem(&1, 1) != "."))
  end

  defp tilt({y, x} = c, {y_max, x_max}, dir, acc) do
    case Map.get(acc, c) do
      "O" ->
        new_acc = Map.delete(acc, c)

        case dir do
          :north -> roll_y(y..1, x, new_acc)
          :south -> roll_y(y..y_max, x, new_acc)
          :west -> roll_x(x..1, y, new_acc)
          :east -> roll_x(x..x_max, y, new_acc)
        end

      _ ->
        acc
    end
  end

  def cycle(map, n) when is_integer(n) do
    maxs = map |> Map.keys() |> Enum.max()
    map = map |> filter_map()

    1..n
    |> Enum.reduce_while(
      {map, %{}, %{}},
      fn x, {acc, memo_i, memo_w} ->
        case memo_i[acc] do
          nil ->
            new_acc = cycle(acc, maxs)
            new_memo_i = Map.put(memo_i, acc, x)
            new_memo_w = Map.put(memo_w, x, north_weight(acc, elem(maxs, 0)))
            {:cont, {new_acc, new_memo_i, new_memo_w}}

          previous ->
            cycle_length = x - previous
            index = rem(n - x, cycle_length) + 1
            {:halt, memo_w[previous + index]}
        end
      end
    )
  end

  def cycle(map, {y_max, x_max} = maxs) do
    [
      {:north, yx(1..y_max, 1..x_max)},
      {:west, xy(1..x_max, 1..y_max)},
      {:south, yx(y_max..1, x_max..1)},
      {:east, xy(x_max..1, y_max..1)}
    ]
    |> Enum.reduce(map, fn {dir, coords}, acc ->
      Enum.reduce(coords, acc, &tilt(&1, maxs, dir, &2))
    end)
  end

  defp roll_y(from..to//by = range, x, acc) do
    Enum.reduce_while(range, acc, fn y, acc ->
      case {y, Map.get(acc, {y, x})} do
        {^to, nil} ->
          {:halt, Map.put(acc, {y, x}, "O")}

        {_, nil} ->
          {:cont, acc}

        _ ->
          {:halt, Map.put(acc, {y - by, x}, "O")}
      end
    end)
  end

  defp roll_x(from..to//by = range, y, acc) do
    Enum.reduce_while(from..to, acc, fn x, acc ->
      case {x, Map.get(acc, {y, x})} do
        {^to, nil} ->
          {:halt, Map.put(acc, {y, x}, "O")}

        {_, nil} ->
          {:cont, acc}

        _ ->
          {:halt, Map.put(acc, {y, x - by}, "O")}
      end
    end)
  end

  def yx(y_range, x_range) do
    for y <- y_range, x <- x_range do
      {y, x}
    end
  end

  def xy(x_range, y_range) do
    for x <- x_range, y <- y_range do
      {y, x}
    end
  end

  def north_weight(map, y_max) do
    map
    |> Enum.map(&weight(&1, y_max))
    |> Enum.sum()
  end

  defp weight({{y, x}, v}, len) do
    case v do
      "O" ->
        len - y + 1

      _ ->
        0
    end
  end
end

input = File.read!("priv/14.txt") |> Aoc.Fourteen.parse()

input |> Aoc.Fourteen.part_one() |> IO.inspect(label: "part 1")
input |> Aoc.Fourteen.part_two() |> IO.inspect(label: "part 2")
1 Like