Powered by AppSignal & Oban Pro

🎄 Year 2020 🔔 Day 11

elixir/notebooks/2020/day11.livemd

🎄 Year 2020 🔔 Day 11

Parse input

filename = "#{__DIR__}/../../../inputs/2020/day11.txt"

asdf =
  """
  L.LL.LL.LL
  LLLLLLL.LL
  L.L.L..L..
  LLLL.LL.LL
  L.LL.LL.LL
  L.LLLLL.LL
  ..L.L.....
  LLLLLLLLLL
  L.LLLLLL.L
  L.LLLLL.LL
  """
  |> String.split("\n")

graphemes =
  File.stream!(filename)
  # asdf
  |> Enum.map(&String.trim/1)
  |> Enum.map(&String.graphemes/1)

width = graphemes |> List.first() |> Enum.count()

parsed_input_stream =
  graphemes
  |> Enum.with_index(fn line, y -> Enum.with_index(line, fn e, x -> {{x, y}, e} end) end)
  |> Enum.concat()
  |> Map.new()

Part One

defmodule Helper do
  def get_adjacent(seats, {x, y}) do
    [{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}]
    |> Enum.map(fn {xd, yd} -> Map.get(seats, {x + xd, y + yd}, ".") end)
  end

  def get_new_seat_value(seats, {coords, current}) do
    new_state =
      case current do
        "." ->
          "."

        "#" ->
          count_adjacent_occupied =
            get_adjacent(seats, coords) |> Enum.count_until(&(&1 == "#"), 4)

          if count_adjacent_occupied >= 4, do: "L", else: "#"

        "L" ->
          all_adjacent_free = get_adjacent(seats, coords) |> Enum.all?(&(&1 != "#"))
          if all_adjacent_free, do: "#", else: "L"
      end

    {coords, new_state}
  end
end

Stream.repeatedly(fn -> nil end)
|> Stream.scan(parsed_input_stream, fn nil, seats ->
  seats
  |> Enum.map(&Helper.get_new_seat_value(seats, &1))
  |> Map.new()
end)
|> Stream.chunk_every(2)
|> Enum.find_value(fn [a, b] -> if a == b, do: a, else: false end)
|> Enum.count(fn {_coords, seat} -> seat == "#" end)

Part two

defmodule Helper do
  def get_visible_coords(seats, {coords, "."}), do: {coords, {[], "."}}

  def get_visible_coords(seats, {coords, current}) do
    [{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}]
    |> Enum.map(fn coord_delta -> get_visible_coord(seats, coords, coord_delta) end)
    |> then(&{coords, {&1, current}})
  end

  defp get_visible_coord(seats, {x, y}, {xd, yd}) do
    case Map.fetch(seats, {x + xd, y + yd}) do
      {:ok, "#"} -> {x + xd, y + yd}
      {:ok, "L"} -> {x + xd, y + yd}
      {:ok, "."} -> get_visible_coord(seats, {x + xd, y + yd}, {xd, yd})
      :error -> {x + xd, y + yd}
    end
  end

  def get_new_seat_value(seats, {coords, {visible_coords, current}}) do
    new_state =
      case current do
        "." ->
          "."

        "#" ->
          count_adjacent_occupied =
            visible_coords
            |> Enum.map(&Map.get(seats, &1, {[], "."}))
            |> Enum.count_until(fn {_, e} -> e == "#" end, 5)

          if count_adjacent_occupied >= 5, do: "L", else: "#"

        "L" ->
          all_adjacent_free =
            visible_coords
            |> Enum.map(&Map.get(seats, &1, {[], "."}))
            |> Enum.all?(fn {_, e} -> e != "#" end)

          if all_adjacent_free, do: "#", else: "L"
      end

    {coords, {visible_coords, new_state}}
  end
end

seats =
  parsed_input_stream
  |> Enum.map(&Helper.get_visible_coords(parsed_input_stream, &1))
  |> Map.new()

Stream.repeatedly(fn -> nil end)
|> Stream.scan(seats, fn nil, seats ->
  seats
  |> Enum.map(&Helper.get_new_seat_value(seats, &1))
  |> Map.new()
end)
|> Stream.chunk_every(2)
|> Enum.find_value(fn [a, b] -> if a == b, do: a, else: false end)
|> Enum.count(fn {_coords, {_visible_coords, seat}} -> seat == "#" end)