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
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:
Today’s super fun. It feels like a variety of Conway’s life game but simpler. My solution is here.
Here’s mine
Also, thanks everyone for sharing your solutions! I’m learning a lot by looking at your code every day
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
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
My solution: y2021/day-11.livemd
For part 1 I cheated a bit and used an Agent
to count flashes
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.
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
# 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)}")
My solution, similar in concept to others, but think there’s a little code variety at least.
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.
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