오징어 게임...은 아니고, 빙고 게임이었다. 여러 개의 빙고판 중 가장 먼저 이기는 판, 가장 늦게 이기는 판을 찾는 문제였다. 다른 언어의 배열과 다르게 엘릭서 리스트는 random access가 금기에 가까워서, 인덱스로 직접 접근했을 때 쉽게 풀리는 이런 문제들이 조금 힘겹다(col_matched
를 보자). 인덱스로 접근할 수 있도록 tuple을 사용해 보려고도 했는데, 단일 값 읽기만 쉽고 값을 변형하기가 또 쉽지 않더라. Enum.all?
이나 Enum.any?
같은 함수 지원도 받고 싶다 보니 결국 리스트로 작성하게 되었다.
타입 안정성을 조금이나마 가져가 보려고 이번에는 상당수의 함수에 타입 명세를 함께 작성해 보았다. 덕분인지 시작 4일만에 결국 100줄이 넘는 코드를 작성하고 말았다.
defmodule Day4 do
@board_size 5
def parse_input do
path = "input/day4.txt"
[draws_raw, _ | boards_raw] =
File.read!(path)
|> String.split("\n")
draws = draws_raw |> drawed_numbers()
boards =
boards_raw
|> Enum.chunk_every(@board_size + 1)
|> Enum.map(&Enum.take(&1, @board_size))
|> Enum.map(&parse_board/1)
{draws, boards}
end
@spec drawed_numbers(String.t()) :: list(number())
defp drawed_numbers(called_number_line) do
called_number_line
|> String.split(",")
|> Enum.map(&String.to_integer/1)
end
@spec parse_board(list(String.t())) :: list(list({number(), boolean()}))
defp parse_board(five_line_input) do
five_line_input
|> Enum.map(fn line ->
line
|> String.trim()
|> String.split(~r{\s+})
|> Enum.map(&{String.to_integer(&1), false})
end)
end
@spec run_q1 :: number
def run_q1 do
{draws, boards} = parse_input()
do_run_q1(draws, boards)
end
@spec run_q2 :: number
def run_q2 do
{draws, boards} = parse_input()
do_run_q2(draws, boards)
end
@spec do_run_q1(list(number), list(list(number))) :: number
defp do_run_q1([current_draw | upcoming_draws], boards) do
boards_after = bingo_single_step(boards, current_draw)
bingo_won = Enum.find(boards_after, &is_bingo/1)
if is_nil(bingo_won) do
do_run_q1(upcoming_draws, boards_after)
else
sum_of_unmarked(bingo_won) * current_draw
end
end
@spec do_run_q2(list(number), list(list(number))) :: number
defp do_run_q2([current_draw | upcoming_draws], boards) do
boards_after = bingo_single_step(boards, current_draw)
bingo_won = Enum.find(boards_after, &is_bingo/1)
cond do
is_nil(bingo_won) ->
do_run_q2(upcoming_draws, boards_after)
length(boards) == 1 ->
sum_of_unmarked(bingo_won) * current_draw
true ->
do_run_q2(upcoming_draws, Enum.reject(boards_after, &is_bingo/1))
end
end
@spec bingo_single_step(list(list(number)), number) :: list(list(number))
defp bingo_single_step(boards, current_draw) do
boards
|> Enum.map(fn board ->
board
|> Enum.map(fn row ->
row
|> Enum.map(fn
{^current_draw, _} -> {current_draw, true}
any -> any
end)
end)
end)
end
defp is_bingo(board) do
raw_matched =
board
|> Enum.any?(fn row ->
Enum.all?(row, fn {_, matched?} -> matched? end)
end)
col_matched =
board
|> Enum.map(fn row ->
row
|> Enum.with_index()
|> Enum.filter(fn {{_value, matched?}, _idx} -> matched? end)
|> Enum.map(&elem(&1, 1))
|> MapSet.new()
end)
|> Enum.reduce(MapSet.new(0..(@board_size - 1)), fn matched_col, acc ->
MapSet.intersection(matched_col, acc)
end)
|> MapSet.size()
|> Kernel.>=(1)
raw_matched or col_matched
end
defp sum_of_unmarked(board) do
board
|> Enum.map(fn row ->
row
|> Enum.reject(fn {_value, matched?} -> matched? end)
|> Enum.map(&elem(&1, 0))
|> Enum.sum()
end)
|> Enum.sum()
end
end
'호두나무 공방 > Advent of Code' 카테고리의 다른 글
Advent of Code 2021 Day 6 in Elixir - Lanternfish (0) | 2021.12.06 |
---|---|
Advent of Code 2021 Day 5 in Elixir - Hydrothermal Venture (0) | 2021.12.06 |
Advent Of Code 2021 Day 3 in Elixir - Binary Diagnostic (0) | 2021.12.03 |
Advent of Code 2021 Day 2 in Elixir - Dive! (0) | 2021.12.02 |
Advent of Code 2021 Day 1 in Elixir - Sonar Sweep (4) | 2021.12.02 |