Aetherus

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

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

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

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

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

Full

Aetherus

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, &not match?({_, {^i, _}}, &1))
        end) or
        Enum.any?(0..4, fn j ->
          Enum.all?(board, &not 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.

Where Next?

Popular in Challenges Top

LostKobrakai
This topic is about Day 9 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/le...
New
JEG2
Note: This topic is to talk about Day 9 of the Advent of Code. For general discussion about the Advent of Code 2018 and links to topics ...
New
gangstead
This is my second year doing AoC in Elixir and my first year doing it with Livebook. When I was doing just plain Elixir I usually set up...
New
shritesh
This was way too easy after the last few days. Simple map, filter and count.
New
bjorng
Note: This topic is to talk about Day 5 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can join...
New
bjorng
This topic is about Day 1 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums): https://adven...
New
bjorng
Note: This topic is to talk about Day 13 of the Advent of Code 2019. There is a private leaderboard for elixirforum members. You can joi...
New
woolfred
It is that time of the year again: Advent of Code 2022 :christmas_tree: Day 1 Leaderboard:
New
New
code-shoily
Here’s my day 3 code This was quite easy. I was afraid Part 2 would be “un-regex-able” and was preparing for hand crafting automata bu...
New

Other popular topics Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement