Aetherus
Advent of Code 2021 - Day 4
This topic is about the Advent of Code 2021 - Day 4.
Thanks to @bjorng , we now have a new Private Leaderboard.
The entry code is:
370884-a6a71927
Most Liked
code-shoily
It’s starting to get difficult already, at least from the perspective of Elixir. It’s difficult to come up with a solution fast that is mutation and early return friendly on a matrix like data.
Aetherus
I usually use %{{x, y} => val} to represent a matrix, but for today’s challenge, I recognized that I need to look up {x, y} by values, so I swapped the keys and the values.
hauleth
Again, LiveBook:
Day 4
Input
This time it is a little bit more convoluted, as there are 2 parts of the input.
Fortunately we can easily disect the parts via pattern matching.
Technically the conversion to the numbers is not needed, but it does no harm
and provides additional layer of safety against some whitespace characters left there
and here.
The Day4.win/2 function is manually unrolled, as it is easier to write than some
random jumping in the list.
[numbers | bingos] =
File.read!("day4.txt")
|> String.split("\n\n", trim: true)
numbers =
numbers
|> String.trim()
|> String.split(",")
|> Enum.map(&String.to_integer/1)
bingos =
bingos
|> Enum.map(fn bingo ->
bingo
|> String.split(~r/\s+/, trim: true)
|> Enum.map(&String.to_integer/1)
end)
defmodule Day4 do
def win(
[
a1, a2, a3, a4, a5,
b1, b2, b3, b4, b5,
c1, c2, c3, c4, c5,
d1, d2, d3, d4, d5,
e1, e2, e3, e4, e5
],
nums
) do
# Rows
all_in([a1, a2, a3, a4, a5], nums) or
all_in([b1, b3, b3, b4, b5], nums) or
all_in([c1, c2, c3, c4, c5], nums) or
all_in([d1, d2, d3, d4, d5], nums) or
all_in([e1, e2, e3, e4, e5], nums) or
# Columns
all_in([a1, b1, c1, d1, e1], nums) or
all_in([a2, b2, c2, d2, e2], nums) or
all_in([a3, b3, c3, d3, e3], nums) or
all_in([a4, b4, c4, d4, e4], nums) or
all_in([a5, b5, c5, d5, e5], nums) or
# Diagonals
all_in([a1, b2, c3, d4, e5], nums) or
all_in([a5, b4, c3, d2, e1], nums)
end
def not_matched(bingo, nums) do
Enum.reject(bingo, &(&1 in nums))
end
defp all_in(list, nums) do
Enum.all?(list, &(&1 in nums))
end
end
Task 1
We simply traverse the numbers list aggregating the numbers (order doesn’t really matter,
here we aggregate them in reverse order to speedup the code). When we have enough numbers
that any of the bingos is winning one, then we halt the reduction and return computed
result.
numbers
|> Enum.reduce_while([], fn elem, acc ->
matches = [elem | acc]
case Enum.find(bingos, &Day4.win(&1, matches)) do
nil -> {:cont, matches}
bingo -> {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem}
end
end)
Task 2
numbers
|> Enum.reduce_while({bingos, []}, fn elem, {bingos, acc} ->
matches = [elem | acc]
case bingos do
[bingo] ->
if Day4.win(bingo, matches) do
{:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem}
else
{:cont, {bingos, matches}}
end
_ ->
{:cont, {Enum.reject(bingos, &Day4.win(&1, matches)), matches}}
end
end)
Whole project can be watched there - https://github.com/hauleth/advent-of-code/blob/master/2021/solutions.livemd
ruslandoga
I represented the boards as binaries,
<<
22, 13, 17, 11, 0,
8, 2, 23, 4, 24,
21, 9, 14, 16, 7,
6, 10, 3, 18, 5,
1, 12, 20, 15, 19
>>
where marked number becomes <<-1::signed>>
defp mark_board(board, number) do
String.replace(board, <<number>>, <<-1>>, global: false)
end
and used binary pattern matching to identify winners:
marked_row = quote(do: <<-1::40-signed>>)
column_patterns = [
quote(do: <<-1::signed, _, _, _, _>>),
quote(do: <<_, -1::signed, _, _, _>>),
quote(do: <<_, _, -1::signed, _, _>>),
quote(do: <<_, _, _, -1::signed, _>>),
quote(do: <<_, _, _, _, -1::signed>>)
]
winning_patterns =
List.flatten([
# rows
quote(do: <<unquote(marked_row), _::bytes>>),
quote(do: <<_::5-bytes, unquote(marked_row), _::bytes>>),
quote(do: <<_::10-bytes, unquote(marked_row), _::bytes>>),
quote(do: <<_::15-bytes, unquote(marked_row), _::bytes>>),
quote(do: <<_::20-bytes, unquote(marked_row)>>),
# columns
for pattern <- column_patterns do
quote(do: <<unquote_splicing(List.duplicate(pattern, 5))>>)
end
])
for pattern <- winning_patterns do
defp winner?(unquote(pattern)), do: true
end
defp winner?(<<_::25-bytes>>), do: false
To calculate the sum I used
def unmarked_sum(board)
Enum.sum(for <<cell::signed <- board>>, cell > 0, do: cell)
end
Aetherus
Today I tried LiveBook on the new Elixir 1.13.0, and accidentally deleted my part 1 code, so I just post my part 2:
[moves | boards] =
File.read!("input.txt")
|> String.split("\n\n", trim: true)
# [integer]
moves =
moves
|> String.split(~r/\D/, trim: true)
|> Enum.map(&String.to_integer/1)
# %{val => {row, col}}
boards =
boards
|> Enum.map(&String.split/1)
|> Enum.map(&Enum.chunk_every(&1, 5))
|> Enum.map(fn board ->
for {row, i} <- Enum.with_index(board), {num, j} <- Enum.with_index(row), into: %{} do
{String.to_integer(num), {i, j}}
end
end)
{_, {move, last_winning_board}} =
for move <- moves, reduce: {boards, nil} do
{boards, last_win} ->
boards = Enum.map(boards, fn board ->
Map.delete(board, move)
end)
{wins, boards} = Enum.split_with(boards, fn board ->
Enum.any?(0..4, fn i ->
Enum.all?(board, ¬ match?({_, {^i, _}}, &1))
end) or
Enum.any?(0..4, fn j ->
Enum.all?(board, ¬ match?({_, {_, ^j}}, &1))
end)
end)
case wins do
[] -> {boards, last_win}
_ -> {boards, {move, List.last(wins)}}
end
end
last_winning_board
|> Map.keys()
|> Enum.sum()
|> Kernel.*(move)
|> IO.inspect()
It feels a little bit tricky implementing a modification-based algorithm in an immutable way.








