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

Day Twenty Three

day23.livemd

Day Twenty Three

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

Section

input = Kino.Input.textarea("Input")
defmodule DayTwentyThree do
  @north {0, -1}
  @south {0, 1}
  @west {-1, 0}
  @east {1, 0}

  def solve1(input) do
    elves = parse(input)

    dirs = [@north, @south, @west, @east]

    {elves, _} =
      Enum.reduce(1..10, {elves, dirs}, fn i, {elves, dirs} ->
        {step(elves, dirs), rotate(dirs)}
      end)

    xs = Enum.map(elves, &elem(&1, 0))
    ys = Enum.map(elves, &elem(&1, 1))

    min_x = Enum.min(xs)
    max_x = Enum.max(xs)
    min_y = Enum.min(ys)
    max_y = Enum.max(ys)

    (1 + max_x - min_x) * (1 + max_y - min_y) - MapSet.size(elves)
  end

  def solve2(input) do
    elves = parse(input)

    dirs = [@north, @south, @west, @east]

    Stream.iterate(1, &(&1 + 1))
    |> Enum.reduce_while({elves, dirs}, fn i, {elves, dirs} ->
      new_elves = step(elves, dirs)

      if elves == new_elves do
        {:halt, i}
      else
        {:cont, {new_elves, rotate(dirs)}}
      end
    end)
  end

  defp step(elves, dirs) do
    new_elves =
      elves
      |> Enum.filter(fn elf -> alone?(elf, elves) end)
      |> MapSet.new()

    proposed =
      MapSet.difference(elves, new_elves)
      |> Enum.reduce(%{}, fn {ex, ey} = elf, proposed ->
        Enum.find(dirs, fn dir ->
          look(dir, elf)
          |> Enum.all?(fn pt -> !MapSet.member?(elves, pt) end)
        end)
        |> then(fn
          {dx, dy} ->
            Map.update(proposed, {ex + dx, ey + dy}, [elf], fn elves -> [elf | elves] end)

          nil ->
            Map.update(proposed, elf, [elf], fn elves -> [elf | elves] end)
        end)
      end)

    Enum.reduce(proposed, new_elves, fn
      {pt, [_elf]}, elves ->
        MapSet.put(elves, pt)

      {_pt, p_elves}, elves ->
        MapSet.union(elves, MapSet.new(p_elves))
    end)
  end

  defp look(@north, {x, y}) do
    [{x - 1, y - 1}, {x, y - 1}, {x + 1, y - 1}]
  end

  defp look(@south, {x, y}) do
    [{x - 1, y + 1}, {x, y + 1}, {x + 1, y + 1}]
  end

  defp look(@east, {x, y}) do
    [{x + 1, y - 1}, {x + 1, y}, {x + 1, y + 1}]
  end

  defp look(@west, {x, y}) do
    [{x - 1, y - 1}, {x - 1, y}, {x - 1, y + 1}]
  end

  defp alone?({x, y}, elves) do
    [
      {x - 1, y - 1},
      {x, y - 1},
      {x + 1, y - 1},
      {x - 1, y},
      {x + 1, y},
      {x - 1, y + 1},
      {x, y + 1},
      {x + 1, y + 1}
    ]
    |> Enum.all?(fn pt -> !MapSet.member?(elves, pt) end)
  end

  defp rotate([l | ls]) do
    ls ++ [l]
  end

  defp parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.with_index()
    |> Enum.reduce(MapSet.new(), fn {line, y}, acc ->
      line
      |> String.to_charlist()
      |> Enum.with_index()
      |> Enum.reduce(acc, fn {c, x}, acc ->
        if c == ?# do
          MapSet.put(acc, {x, y})
        else
          acc
        end
      end)
    end)
  end

  defp print(elves) do
    xs = Enum.map(elves, &elem(&1, 0))
    ys = Enum.map(elves, &elem(&1, 1))

    min_x = Enum.min(xs)
    max_x = Enum.max(xs)
    min_y = Enum.min(ys)
    max_y = Enum.max(ys)

    for y <- min_y..max_y do
      for x <- min_x..max_x do
        IO.write(if MapSet.member?(elves, {x, y}), do: "#", else: ".")
      end

      IO.puts("")
    end

    IO.puts("")
    IO.puts("")
  end
end

DayTwentyThree.solve2(Kino.Input.read(input))