Powered by AppSignal & Oban Pro

AoC 2022 Day 14

2022/day14.livemd

AoC 2022 Day 14

Mix.install([:kino])

defmodule Utils do
  def split(line, sep \\ "") do
    String.split(line, sep, trim: true)
  end

  def split_all_lines(text, sep \\ "") do
    text
    |> String.split("\n", trim: true)
    |> Enum.map(&split(&1, sep))
  end

  def to_numbers(number) when is_binary(number) do
    String.to_integer(number)
  end

  def to_numbers(numbers) when is_list(numbers) do
    Enum.map(numbers, &to_numbers/1)
  end

  def to_matrix(text, sep \\ "") do
    text
    |> split_all_lines(sep)
    |> then(fn data ->
      for {row, r} <- Enum.with_index(data), {col, c} <- Enum.with_index(row) do
        {{r, c}, col}
      end
    end)
    |> Map.new()
  end
end

Setup

import Utils
input = Kino.Input.textarea("Input:")
text = Kino.Input.read(input)
data = split_all_lines(text, [" -> ", ","]) |> to_numbers()

map =
  for path <- data do
    for [[x1, y1], [x2, y2]] <- Enum.chunk_every(Enum.chunk_every(path, 2), 2, 1, :discard) do
      cond do
        x1 == x2 -> for y <- y1..y2, do: {x1, y}
        y1 == y2 -> for x <- x1..x2, do: {x, y1}
      end
    end
  end
  |> List.flatten()
  |> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
  |> Map.new(fn {k, v} -> {k, v |> MapSet.new() |> Enum.sort()} end)

P1

defmodule P1 do
  def solve(map) do
    Stream.iterate(
      map,
      fn map ->
        case find_next(map, 500, 0) do
          nil ->
            nil

          {x, y} ->
            %{map | x => List.insert_at(map[x], Enum.find_index(map[x], &(&1 > y)), y)}
        end
      end
    )
    |> Stream.take_while(&(!is_nil(&1)))
    |> Enum.count()
    |> Kernel.-(1)
  end

  def find_next(map, x, y) do
    c = first(map, x, y)
    [l, r] = Enum.map([x - 1, x + 1], &first(map, &1, c))

    case {l, c, r} do
      {_, nil, _} -> nil
      {l, c, _r} when l > c -> find_next(map, x - 1, l)
      {_l, c, r} when r > c -> find_next(map, x + 1, r)
      {_l, c, _r} -> {x, c - 1}
    end
  end

  def first(_map, _x, nil) do
    nil
  end

  def first(map, x, y) do
    map |> Map.get(x, []) |> Enum.drop_while(&(&1 < y)) |> List.first()
  end
end

P1.solve(map)

P2

defmodule P2 do
  def solve(map) do
    max = map |> Map.values() |> List.flatten() |> Enum.max()

    Stream.iterate(
      map,
      fn map ->
        case find_next(map, 500, 0) do
          {500, 0} ->
            nil

          {x, nil} ->
            Map.update(map, x, [max + 1], &(&1 ++ [max + 1]))

          {x, y} ->
            %{map | x => List.insert_at(map[x], Enum.find_index(map[x], &(&1 > y)), y)}
        end
      end
    )
    |> Stream.take_while(&(!is_nil(&1)))
    |> Enum.count()
  end

  def find_next(map, x, y) do
    c = first(map, x, y)
    [l, r] = Enum.map([x - 1, x + 1], &first(map, &1, c))

    case {l, c, r} do
      {_, nil, _} -> {x, nil}
      {l, c, _r} when l > c -> find_next(map, x - 1, l)
      {_l, c, r} when r > c -> find_next(map, x + 1, r)
      {_l, c, _r} -> {x, c - 1}
    end
  end

  def first(_map, _x, nil) do
    nil
  end

  def first(map, x, y) do
    map |> Map.get(x, []) |> Enum.drop_while(&(&1 < y)) |> List.first()
  end
end

P2.solve(map)