Day 04
Setup
Mix.install([:kino])
input = Kino.Input.textarea("Puzzle Input")
Part 1 First Try module
defmodule Day4 do
@base for i <- 0..4, do: i
columns =
@base
|> Enum.map(fn b ->
for b2 <- @base, do: b + 5 * b2
end)
rows =
@base
|> Enum.map(&(&1 * 5))
|> Enum.map(fn rs ->
for b <- @base, do: rs + b
end)
# NO DIAGONALS!
# diagonals =
# for fun <- [& &1, &(4 - &1)] do
# for b <- @base, do: Enum.at(rows, b) |> Enum.at(fun.(b))
# end
@bingos columns ++ rows
def process_input(input) do
[draws_input | boards_input] =
input
|> Kino.Input.read()
|> String.split("\n", trim: true)
draws =
draws_input
|> String.split(",")
|> Enum.map(&String.to_integer/1)
boards =
boards_input
|> Enum.chunk_every(5)
|> Enum.map(
&(&1
|> Enum.join(" ")
|> String.split(" ", trim: true)
|> Enum.map(fn square -> String.to_integer(square) end)
# append empty [] for tracking marked squares
|> then(fn board -> {board, []} end))
)
{boards, draws}
end
def mark_boards(boards, draw) do
boards
|> Enum.map(&mark_board(&1, draw))
end
def mark_board({board, marks}, draw) do
index = Enum.find_index(board, &(&1 == draw))
{board, marks ++ [index]}
end
def process({boards, []}), do: boards
def process({boards, [draw | draw_tail]}) do
process({mark_boards(boards, draw), draw_tail})
end
def is_bingo?({_board, marks}) when length(marks) > 4 do
for bingo <- @bingos do
bingo
|> Enum.reject(&(&1 in marks))
|> then(&(length(&1) == 0))
end
|> Enum.any?()
end
def is_bingo?(_), do: false
# Start on 1, not 0
def draw_numbers(marked_boards, round) do
marked_boards
|> Enum.map(fn {board, marks} ->
is_bingo?({board, Enum.slice(marks, 0, round)})
end)
end
def play_bingo(marked_boards, round, []) do
board_counts = for i <- 0..(length(marked_boards) - 1), do: i
bingo_status = draw_numbers(marked_boards, round)
bingo_boards =
Enum.reduce(board_counts, [], fn i, bboards ->
case Enum.at(bingo_status, i) do
true ->
[Enum.at(marked_boards, i) | bboards]
false ->
bboards
end
end)
play_bingo(marked_boards, round + 1, bingo_boards)
end
def play_bingo(_, round, bingo_boards) do
# I'm leaving this here!
IO.puts("BINGO!")
# assume only one bingo for now
[{board, marks} | _] = bingo_boards
used_marks = Enum.slice(marks, 0, round - 1)
final_num = Enum.at(board, Enum.at(used_marks, -1))
{nillify_board(board, used_marks), final_num}
end
def nillify_board(board, [nil | mark_tail]) do
nillify_board(board, mark_tail)
end
def nillify_board(board, [mark | mark_tail]) do
List.replace_at(board, mark, nil)
|> nillify_board(mark_tail)
end
def nillify_board(board, []) do
board
end
end
Part 1 First Try exec
Day4.process_input(input)
|> Day4.process()
|> Day4.play_bingo(1, [])
|> then(fn {squares, bingo} ->
squares
|> Enum.filter(&(&1 != nil))
|> Enum.sum()
|> then(fn sum -> sum * bingo end)
end)
The Jose Way module
defmodule Board do
empty_board = Tuple.duplicate(Tuple.duplicate(false, 5), 5)
@enforce_keys [:numbers]
defstruct numbers: %{}, grid: empty_board
def new(numbers) when is_map(numbers) do
%Board{numbers: numbers}
end
def mark(%Board{numbers: numbers} = board, number) do
case numbers do
%{^number => {row, col}} ->
put_in(board, [Access.key(:grid), Access.elem(row), Access.elem(col)], true)
%{} ->
board
end
end
def unmarked_sum(%Board{grid: grid, numbers: numbers}) do
Enum.sum(
for {number, {row, col}} <- numbers,
grid |> elem(row) |> elem(col) == false,
do: number
)
end
def won?(%Board{grid: grid}) do
row_won?(grid) or column_won?(grid)
end
defp column_won?(grid) do
Enum.any?(0..4, fn col ->
Enum.all?(0..4, fn row -> grid |> elem(row) |> elem(col) end)
end)
end
defp row_won?(grid) do
Enum.any?(0..4, fn row ->
elem(grid, row) == {true, true, true, true, true}
end)
end
end
The Jose Way exec
[numbers | grids] =
input
|> Kino.Input.read()
|> String.split("\n", trim: true)
boards =
grids
|> Enum.chunk_every(5)
|> Enum.map(fn rows ->
Board.new(
for {line, row} <- Enum.with_index(rows, 0),
{number, col} <- Enum.with_index(String.split(line), 0),
into: %{} do
{String.to_integer(number), {row, col}}
end
)
end)
{number, board = %Board{}} =
numbers
|> String.split(",")
|> Enum.map(&String.to_integer/1)
|> Enum.reduce_while(boards, fn number, boards ->
boards = Enum.map(boards, &Board.mark(&1, number))
if board = Enum.find(boards, &Board.won?/1) do
{:halt, {number, board}}
else
{:cont, boards}
end
end)
number * Board.unmarked_sum(board)
Module Refactor
defmodule MyBoard do
def process_input(input) do
[raw_numbers | raw_boards] =
input
|> Kino.Input.read()
|> String.split("\n", trim: true)
numbers =
raw_numbers
|> String.split(",")
|> Enum.map(&String.to_integer/1)
boards =
raw_boards
|> Enum.chunk_every(5)
|> Enum.map(fn rows ->
for {line, row} <- Enum.with_index(rows),
{number, col} <- Enum.with_index(String.split(line)) do
%{String.to_integer(number) => {row, col, false}}
end
|> Enum.reduce(%{}, fn square, acc -> Map.merge(acc, square) end)
end)
{numbers, boards}
end
def play({numbers, boards}) do
numbers
|> Enum.reduce_while(boards, fn number, boards ->
marked_boards = Enum.map(boards, &MyBoard.mark(&1, number))
if board = Enum.find(marked_boards, &MyBoard.won?/1) do
{:halt, {number, board}}
else
{:cont, marked_boards}
end
end)
end
def play_all([board], [number | _]) do
{number, MyBoard.mark(board, number)}
end
def play_all(boards, [number | numbers]) do
boards
|> Enum.map(&MyBoard.mark(&1, number))
|> Enum.reject(&MyBoard.won?/1)
|> MyBoard.play_all(numbers)
end
def mark(board, number) do
case board do
%{^number => {row, col, false}} ->
put_in(board, [number], {row, col, true})
_ ->
board
end
end
def won?(board) do
marks = Map.filter(board, fn {_, {_, _, b}} -> b end)
if length(Map.keys(marks)) >= 5 do
[rows, cols, _] =
marks
|> Map.values()
|> List.zip()
|> Enum.map(
&(&1
|> Tuple.to_list()
|> Enum.frequencies()
|> Map.values())
)
Enum.any?(rows, &(&1 == 5)) or Enum.any?(cols, &(&1 == 5))
end
end
def unmarked_sum(board) do
board
|> Map.reject(fn {_, {_, _, b}} -> b end)
|> Map.keys()
|> Enum.sum()
end
end
Part 1 Refactor exec
MyBoard.process_input(input)
|> MyBoard.play()
|> then(fn {number, board} ->
MyBoard.unmarked_sum(board) * number
end)
Part 2 exec
{numbers, boards} = MyBoard.process_input(input)
{number, board} = MyBoard.play_all(boards, numbers)
number * MyBoard.unmarked_sum(board)
Scratch