Advent of Code - Day 4
Mix.install([
{:kino_aoc, "~> 0.1"}
])
Introduction
–> Content
Puzzle
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2021", "4", System.fetch_env!("LB_AOC_SESSION"))
Parser
Code - Parser
defmodule Parser do
@spec parse(String.t()) :: [%{integer() => [String.t()]}]
def parse(input) do
String.split(input, "\n", trim: true)
|> Enum.map(fn line ->
Regex.replace(~r/Card\s+\d+\: /, line, "")
|> String.split(" | ")
|> Enum.map(fn line ->
String.split(line, " ", trim: true) |> Enum.map(&String.to_integer(&1))
end)
|> (fn data -> [card_number(line)] ++ data end).()
|> Enum.zip([:card, :winning, :yours])
|> Map.new(fn {val, key} -> {key, val} end)
end)
end
defp card_number(line) do
Regex.run(~r/Card\s+(\d+)\: /, line)
|> Enum.at(1)
|> String.to_integer()
end
end
Tests - Parser
ExUnit.start(autorun: false)
defmodule ParserTest do
use ExUnit.Case, async: true
import Parser
@input """
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
"""
@expected [
%{card: 1, winning: [41, 48, 83, 86, 17], yours: [83, 86, 6, 31, 17, 9, 48, 53]},
%{card: 2, winning: [13, 32, 20, 16, 61], yours: [61, 30, 68, 82, 17, 32, 24, 19]},
%{card: 3, winning: [1, 21, 53, 59, 44], yours: [69, 82, 63, 72, 16, 21, 14, 1]},
%{card: 4, winning: [41, 92, 73, 84, 69], yours: [59, 84, 76, 51, 58, 5, 54, 83]},
%{card: 5, winning: [87, 83, 26, 28, 32], yours: [88, 30, 70, 12, 93, 22, 82, 36]},
%{card: 6, winning: [31, 18, 13, 56, 72], yours: [74, 77, 10, 23, 35, 67, 36, 11]}
]
test "parse test" do
actual = parse(@input)
assert actual == @expected
end
end
ExUnit.run()
Part One
Code - Part 1
defmodule PartOne do
def solve(input) do
IO.puts("--- Part One ---")
IO.puts("Result: #{run(input)}")
end
def run(input_string) do
input = Parser.parse(input_string)
Enum.map(input, fn %{winning: winning, yours: yours} ->
winners_for_card = find_winning_numbers(winning, yours)
calculate_points(winners_for_card)
end)
|> Enum.sum()
end
def find_winning_numbers(winning, yours) do
{res, _} =
Enum.reduce(yours, {[], winning}, fn num, {res, winning} ->
if num in winning do
{[num | res], winning -- [num]}
else
{res, winning}
end
end)
res
end
def calculate_points(winners) do
cond do
Enum.count(winners) == 0 -> 0
Enum.count(winners) == 1 -> 1
true -> Integer.pow(2, Enum.count(winners) - 1)
end
end
def run2(input_string) do
input_string
end
end
Tests - Part 1
ExUnit.start(autorun: false)
defmodule PartOneTest do
use ExUnit.Case, async: true
import PartOne
@input """
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
"""
@expected 13
test "simple example" do
actual = run(@input)
assert actual == @expected
end
end
ExUnit.run()
Solution - Part 1
PartOne.solve(puzzle_input)
# PartOne.run2(puzzle_input)
Part Two
Code - Part 2
defmodule PartTwo do
def solve(input) do
IO.puts("--- Part Two ---")
IO.puts("Result: #{run(input)}")
end
def run(input_string) do
input_string
|> Parser.parse()
|> wins_per_card()
|> won_card_quantities()
|> Map.values()
|> Enum.sum()
end
defp wins_per_card(input) do
Enum.map(input, fn %{card: card, winning: winning, yours: yours} ->
winners_for_card = PartOne.find_winning_numbers(winning, yours)
%{card: card, wins: Enum.count(winners_for_card)}
end)
end
defp won_card_quantities(input) do
base_result = Map.new(input, fn %{card: card, wins: wins} -> {card, 1} end)
Enum.reduce(input, base_result, fn %{card: card, wins: wins}, acc ->
if wins >= 1 do
Enum.reduce((card + 1)..(card + wins), acc, fn won_card, acc1 ->
Map.update(acc1, won_card, 0, fn val -> val + 1 * acc1[card] end)
end)
else
acc
end
end)
end
end
Tests - Part 2
ExUnit.start(autorun: false)
defmodule PartTwoTest do
use ExUnit.Case, async: true
import PartTwo
@input """
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
"""
@expected 30
test "simple example" do
actual = run(@input)
assert actual == @expected
end
end
ExUnit.run()
Solution - Part 2
PartTwo.solve(puzzle_input)