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

First 10 of 32 Posts Switch mode

bjorng

bjorng

Erlang Core Team
Aetherus

Aetherus OP

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.

Aetherus

Aetherus OP

Sorry for posting twice. There was a network issue when I tried to edit my answer, and I don’t know how to delete one.

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 OP

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.

code-shoily

code-shoily

Here I am thinking the opposite direction, I am thinking %{value => {row, col}} where row = div(idx, 5) and col = rem(idx, 5). That way I can just remove the value into a bucket of rowlist, collist as I move forward. But it’s feeling slightly weird and verbose.

Update: Seems like you’re doing the same thing. Sorry, brain’s not keeping up with eyes lol.

code-shoily

code-shoily

Ah, finally got the first one right, also, I got to use the new Map.map :smiley:

UPDATE: Did both parts, Part 2 was quicker to implement, but I don’t have a good feeling about this code for some reason.

defmodule AdventOfCode.Y2021.Day04 do
  @moduledoc """
  --- Day 4: Giant Squid ---
  Problem Link: https://adventofcode.com/2021/day/4
  """
  use AdventOfCode.Helpers.InputReader, year: 2021, day: 4

  def run_1, do: input!() |> parse() |> play_1()
  def run_2, do: input!() |> parse() |> play_2()

  def parse(data) do
    [order_data | board_data] = String.split(data, "\n\n", trim: true)
    {get_orders(order_data), get_boards(board_data)}
  end

  defp get_orders(data), do: data |> String.split(",") |> Enum.map(&String.to_integer/1)

  defp get_boards(data), do: data |> Enum.with_index() |> Enum.map(&get_board/1)

  defp get_board({data, idx}) do
    data
    |> String.split("\n")
    |> Enum.flat_map(fn line ->
      line
      |> String.split(" ", trim: true)
      |> Enum.map(&String.to_integer/1)
    end)
    |> Enum.with_index()
    |> Enum.into(%{})
    |> Map.map(fn {_, idx} -> {div(idx, 5), rem(idx, 5)} end)
    |> then(&board_state(&1, idx))
  end

  defp board_state(board, idx), do: {idx, board, [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]}

  def play_1({orders, board_states}) do
    orders
    |> Enum.reduce_while(board_states, fn order, acc ->
      acc = Enum.map(acc, &mark_board(&1, order))

      case get_winner(acc) do
        nil -> {:cont, acc}
        board -> {:halt, {score(board), order}}
      end
    end)
    |> Tuple.product()
  end

  def score(board), do: board |> Map.keys() |> Enum.sum()

  defp mark_board({idx, board, rows, cols} = state, order) do
    case Map.pop(board, order) do
      {{row, col}, board} ->
        {idx, board, List.update_at(rows, row, &(&1 + 1)), List.update_at(cols, col, &(&1 + 1))}

      {nil, _} ->
        state
    end
  end

  defp get_winner(board_states) do
    Enum.reduce_while(board_states, nil, fn {_, board, rows, cols}, _ ->
      5 in rows or (5 in cols && {:halt, board}) || {:cont, nil}
    end)
  end

  def play_2({orders, board_states}) do
    orders
    |> Enum.reduce({board_states, []}, fn order, {acc, winners} ->
      acc = Enum.map(acc, &mark_board(&1, order))

      {acc,
       [Enum.map(get_winners(acc), fn {idx, board} -> {idx, score(board), order} end) | winners]}
    end)
    |> elem(1)
    |> Enum.reverse()
    |> Enum.drop_while(&Enum.empty?/1)
    |> Enum.flat_map(&Function.identity/1)
    |> last_winner(Enum.count(board_states), MapSet.new())
    |> Tuple.product()
  end

  defp last_winner([{idx, score, order} | rest], size, map_set) do
    set = MapSet.put(map_set, idx)
    (Enum.count(set) == size && {score, order}) || last_winner(rest, size, set)
  end

  defp get_winners(board_states) do
    Enum.reduce(board_states, [], fn {idx, board, rows, cols}, acc ->
      ((5 in rows or 5 in cols) && [{idx, board} | acc]) || acc
    end)
  end
end
wasi0013

wasi0013

My solution: y2021/day_04.ex

Klohto

Klohto

I’m already ashamed by posting this horrible Part 1.
At this point, I’ll have to switch to normal function definitions because it’s becoming a spaghetti mess with anons and it’s just Day 4.

Don’t ask how I did Part 2, I have to clean both solutions up first.
It’s a challenge trying to do this in an immutable way.

# Day 4

## Input

<!-- livebook:{"livebook_object":"cell_input","name":"input","type":"text","value":"7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1  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   3 15  0  2 22  9 18 13 17  5 19  8  7 25 23 20 11 10 24  4 14 21 16 12  6  14 21 17 24  4 10 16 15  9 19 18  8 23 26 20 22 11 13  6  5  2  0 12  3  7"} -->

```elixir
# Livebook Input (atleast the default one) doesn't support multiline
[winning_nums | boards] = IO.gets(:input) |> String.split(~r{\s}, trim: true)

winning_nums =
  winning_nums
  |> String.split(",")
  |> Enum.map(&String.to_integer/1)

boards =
  boards
  |> Enum.map(&String.to_integer/1)
  |> Enum.chunk_every(5)
  |> Enum.chunk_every(5)
```

## Part 1

```elixir
calculate_card = fn bingo_card, last_number ->
  List.flatten(bingo_card)
  |> Enum.reject(&(&1 == -1))
  |> Enum.sum()
  |> Kernel.*(last_number)
  |> IO.inspect()

  # exit after first find
  Kernel.exit(:ok)
end

# mark all matching numbers inside boards
mark_boards = fn boards, n ->
  List.flatten(boards)
  |> Enum.map(&if(&1 == n, do: -1, else: &1))
  # convert back to 5x5 boards
  |> Enum.chunk_every(5)
  |> Enum.chunk_every(5)
end

check_boards = fn boards, last_n ->
  Enum.each(boards, fn b ->
    for i <- 0..4 do
      # horizontal || vertical
      if Enum.sum(Enum.at(b, i)) == -5 || Enum.sum(Enum.map(b, &Enum.at(&1, i))) == -5 do
        calculate_card.(b, last_n)
      end
    end
  end)

  boards
end

Enum.reduce(winning_nums, boards, fn n, b -> mark_boards.(b, n) |> check_boards.(n) end)
```

## Part 2

```elixir
# So ugly I had to hide it
```

Where Next?

Trending in Challenges Top

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New

We're in Beta

About us Mission Statement