Advent of Code 2021 - Day 11

This topic is about Day 11 of the Advent of Code 2021.

We have a private leaderboard (shared with users of Erlang Forums):

https://adventofcode.com/2021/leaderboard/private/view/370884

The entry code is:
370884-a6a71927

Here is my solution:

1 Like
2 Likes

Today’s super fun. It feels like a variety of Conway’s life game but simpler. My solution is here.

2 Likes

Here’s mine: y2021/Day_11.ex

1 Like

Here’s mine

Also, thanks everyone for sharing your solutions! I’m learning a lot by looking at your code every day :hugs:

1 Like

I didn’t have too much time today, so for part 2 I went for the quick and dirty solution of changing the part 1 reduce to an arbitrary high number, printing the step at each iteration, and doing a raise(RuntimeError) in the flash function when the conditions are met. So the last printed number before the RuntimeError is the solution :smiley:

defmodule AdventOfCode.Day11 do
  @dirs [
    {-1, -1},
    {-1, 0},
    {-1, 1},
    {0, -1},
    {0, 1},
    {1, -1},
    {1, 0},
    {1, 1}
  ]

  def part1(input) do
    grid = parse_input(input)

    {_, flashes} =
      Enum.reduce(1..100, {grid, 0}, fn _, {grid, flashes} -> step(grid, flashes) end)

    flashes
  end

  def part2(input) do
    grid = parse_input(input)

    Enum.reduce(1..999_999, {grid, 0}, fn n, {grid, flashes} ->
      IO.puts(n)
      step(grid, flashes)
    end)
  end

  def step(grid, flashes \\ 0) do
    {grid, new_flashes} =
      grid
      |> increase_energy()
      |> flash()

    {reset_levels(grid), flashes + new_flashes}
  end

  def increase_energy(grid) do
    Map.new(grid, fn {k, v} -> {k, v + 1} end)
  end

  def flash(grid, flashed \\ MapSet.new()) do
    # i feel dirty
    if MapSet.size(flashed) == 100, do: raise(RuntimeError)

    to_flash =
      grid
      |> Enum.filter(fn {_k, v} -> v > 9 end)
      |> Enum.map(fn {k, _v} -> k end)
      |> MapSet.new()

    if to_flash == flashed do
      {grid, MapSet.size(flashed)}
    else
      flash(update_flashed(grid, flashed), to_flash)
    end
  end

  def update_flashed(grid, flashed) do
    Map.new(grid, fn {{x, y}, v} ->
      nbs =
        Enum.count(@dirs, fn {nx, ny} ->
          new_point = {x + nx, y + ny}
          Map.get(grid, new_point, 0) > 9 and new_point not in flashed
        end)

      {{x, y}, v + nbs}
    end)
  end

  def reset_levels(grid) do
    Map.new(grid, fn {k, v} -> if v > 9, do: {k, 0}, else: {k, v} end)
  end

  def parse_input(input) do
    lines = String.split(input, "\n", trim: true)

    for {row, x} <- Enum.with_index(lines),
        {col, y} <- Enum.with_index(String.codepoints(row)),
        into: %{} do
      {{x, y}, String.to_integer(col)}
    end
  end
end
1 Like

My solution: y2021/day-11.livemd

For part 1 I cheated a bit and used an Agent to count flashes :grinning_face_with_smiling_eyes:

1 Like

Not sure why I struggled so much getting my flash calculation to work correctly. Eventually realized that I was not flashing as cell values were updated but at the end of each update for the whole grid. Anyway not super happy with it but it worked.

1 Like

I am currently learning Elixir by completing advent of code. I have been using these daily threads to try and improve my original solution, today I took inspiration from @qhwa and @bjorng and made something that combines ideas from those solutions and my original.

Let me know what you think :slightly_smiling_face:

# Title: Day 11

# ── Setup ──

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

# ── Inputs ──

input = Kino.Input.textarea("Please enter the puzzle input:")

lines =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)

# ── Solution ──

defmodule Recursion do
  @offsets [{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}]

  def part1(lines, steps \\ 100) do
    {parse(lines), 0}
    |> Stream.iterate(&recur/1)
    |> Stream.drop(steps)
    |> get_answer
  end

  def part2(lines) do
    {parse(lines), 0}
    |> Stream.iterate(&recur/1)
    |> Stream.with_index()
    |> Stream.drop_while(fn {{grid, _}, _} ->
      Enum.any?(grid, fn {_, level} -> level != 0 end)
    end)
    |> get_answer
  end

  defp parse(lines) do
    for {line, row} <- Enum.with_index(lines),
        {value, column} <- Enum.with_index(String.graphemes(line)),
        into: %{} do
      {{row, column}, String.to_integer(value)}
    end
  end

  defp get_answer(stream) do
    stream |> Enum.take(1) |> Enum.at(0) |> elem(1)
  end

  defp recur({grid, num_flashes}) do
    grid =
      Enum.reduce(grid, grid, fn {key, _value}, grid ->
        increase(grid, key)
      end)
      |> reset_flashes

    {grid, num_flashes + Enum.count(grid, fn {_key, value} -> value == 0 end)}
  end

  defp increase(grid, key) do
    case grid do
      %{^key => 9} -> flash(grid, key)
      %{^key => n} -> Map.put(grid, key, n + 1)
      %{} -> grid
    end
  end

  defp flash(grid, key) do
    grid = Map.update!(grid, key, &(&1 + 1))

    neighbours(grid, key)
    |> Enum.reduce(grid, fn neighbour, grid ->
      increase(grid, neighbour)
    end)
  end

  defp neighbours(grid, {row, col}) do
    @offsets
    |> Enum.map(fn {x, y} -> {row + x, col + y} end)
    |> Enum.filter(&Map.has_key?(grid, &1))
  end

  defp reset_flashes(grid) do
    grid
    |> Enum.map(fn {key, level} -> if level > 9, do: {key, 0}, else: {key, level} end)
    |> Enum.into(%{})
  end
end

IO.puts("Part 1: #{Recursion.part1(lines)}")
IO.puts("Part 2: #{Recursion.part2(lines)}")
3 Likes

My solution, similar in concept to others, but think there’s a little code variety at least.

1 Like

Like @worseforwear I’ve also been using Advent of Code as a way to learn Elixir. My solutions aren’t always the cleanest but I’m learning a lot reading everyone’s code. I’ve been trying to solve the puzzles before checking here.

Here’s my solution for today

2 Likes

part 1:

part 2:

After using Enum.reduce for part 1, it seemed appropriate to go with Enum.reduce_while for part 2:

def part_2 do
  init_map = parse_input()

  Enum.reduce_while(1..9999, init_map, fn step, acc ->
    after_step = run_step(acc)

    if count_flashed(after_step) == 100 do
      {:halt, step}
    else
      {:cont, Map.map(after_step, &reset_to_ready/1)}
    end
  end)
end

Full solution here