Skip to content

Latest commit

 

History

History
169 lines (139 loc) · 3.38 KB

day4.livemd

File metadata and controls

169 lines (139 loc) · 3.38 KB

Advent 2021 - Day 4

Setup

Mix.install([
  {:kino, github: "livebook-dev/kino"}
])
input = Kino.Input.textarea("Please paste your input file:")
input =
  input
  |> Kino.Input.read()
  |> String.trim()
  |> String.split("\n")

{numbers, list} = List.pop_at(input, 0)

# pluck out the first row of numbers
numbers =
  numbers
  |> String.split(",")
  |> Enum.map(&String.to_integer(&1))

# parse the remaining chunks of text into boards
{_, boards} =
  0..div(length(list) - 1, 6)
  |> Enum.reduce({list, []}, fn _, {list, boards} ->
    board =
      Enum.take(list, 6)
      |> Enum.map(&String.split(&1, " "))
      |> List.flatten()
      |> Enum.filter(&(&1 != ""))
      |> Enum.map(&String.to_integer(&1))

    list = Enum.drop(list, 6)
    {list, [board | boards]}
  end)

# create 'players', holding some state if they have won / what their winning_number was
players =
  boards
  |> Enum.map(fn board ->
    %{
      :winner => false,
      :board => Enum.map(board, &%{:value => &1, :marked => false}),
      :winning_number => nil
    }
  end)

Utils

defmodule Utils do
  @doc """
  Check if a given board has won
  """
  def board_winning(board) do
    horizontals = Enum.chunk_every(board, 5)

    verticals =
      0..4
      |> Enum.map(fn offset ->
        board
        |> Enum.drop(offset)
        |> Enum.take_every(5)
      end)

    Enum.concat([horizontals, verticals])
    |> Enum.any?(fn line ->
      Enum.all?(line, & &1.marked)
    end)
  end
end

Part 1

%{:winner => winner, :final_number => final_number} =
  numbers
  |> Enum.reduce_while(players, fn number, players ->
    players =
      players
      |> Enum.map(fn %{:board => board} ->
        board =
          Enum.map(board, fn tile ->
            if tile.value == number do
              %{tile | marked: true}
            else
              tile
            end
          end)

        %{:winner => Utils.board_winning(board), :board => board}
      end)

    winner = Enum.find(players, & &1.winner)

    if winner != nil do
      {:halt, %{:winner => winner, :final_number => number}}
    else
      {:cont, players}
    end
  end)

unmarked_sum =
  Enum.filter(winner.board, &(&1.marked != true))
  |> Enum.map(& &1.value)
  |> Enum.sum()

unmarked_sum * final_number

Part 2

players =
  numbers
  |> Enum.reduce(players, fn number, players ->
    players =
      players
      |> Enum.map(fn player = %{:board => board} ->
        board =
          Enum.map(board, fn tile ->
            if tile.value == number do
              %{tile | marked: true}
            else
              tile
            end
          end)

        if !player.winner && Utils.board_winning(board) do
          %{player | :winner => true, :winning_number => number, :board => board}
        else
          if player.winner do
            player
          else
            %{player | :board => board}
          end
        end
      end)

    players
  end)

winning_numbers = Enum.map(players, & &1.winning_number)

least_epic_winning_number =
  Enum.reverse(numbers)
  |> Enum.find(&Enum.member?(winning_numbers, &1))

least_epic = Enum.find(players, &(&1.winning_number == least_epic_winning_number))

unmarked_sum =
  Enum.filter(least_epic.board, &(&1.marked != true))
  |> Enum.map(& &1.value)
  |> Enum.sum()

unmarked_sum * least_epic_winning_number