Day 04: Scratchcards
Now I'm so happy with the parser combinator! We need just one more combinator, some_p
for one or more matches. Also, let's add the token
abbreviation.
#parsers
some_p(p::Parser) = sequence(p, many_p(p)) >>
starmap((first, rest) -> pushfirst!(rest, first))
token = token_p ∘ match_p
#day04
using ..Parsing: token, sequence, skip, fmap, starmap, some_p
struct Card
number::Int
winning::Set{Int}
trial::Vector{Int}
end
unsigned = token(r"\d+") >> fmap(x -> parse(Int, x.match))
card_p = sequence(
token("Card") >>> unsigned >> skip(token(":")),
some_p(unsigned) >> skip(token("|")) >> fmap(Set),
some_p(unsigned)) >> starmap(Card)
For part 1, we need to compute the score
#day04
wins(c::Card) = sum(c.trial .∈ (c.winning,))
score(c::Card) = let x = wins(c)
x > 0 ? 2^(x - 1) : 0
end
For part 2, we get our first dynamic programming exercise. I made the mistake of coping score(c)
amount of cards, getting me into ridiculous numbers.
#day04
function play(cards::Vector{Card})
copies = Vector{Union{Int,Nothing}}(nothing, length(cards))
function f(n::Int)
if n > length(copies)
return 0
end
if isnothing(copies[n])
s = wins(cards[n])
copies[n] = sum(f.(n+1:n+s); init=1)
end
return copies[n]
end
end
Maybe this was needlesly complicated, this is much simpler:
#day04
function play2(cards::Vector{Card})
copies = ones(Int, length(cards))
for (i, c) in enumerate(cards)
copies[i+1:i+wins(c)] .+= copies[i]
end
copies
end
Main
file: src/Day04.jl
module Day04
<<day04>>
function main(io::IO)
input = readlines(io) .|> (first ∘ card_p)
println("Part 1: ", input .|> score |> sum)
println("Part 2: ", input |> play2 |> sum)
end
end
Plot
#| creates: docs/fig/day04-ncards.png
#| requires: src/Day04.jl input/day04.txt
#| collect: figures
using CairoMakie
using AOC2023.Day04: card_p, play2, wins
CairoMakie.activate!()
cards = open(readlines, "input/day04.txt", "r") .|> (first ∘ card_p)
fig = Figure(size=(600, 1000))
ax = Axis(fig[2,1], yscale=log2)
barplot!(ax, 1:length(cards), play2(cards))
function stack(ws)
height = zeros(Int, length(ws))
segments = NTuple{2,Int}[]
for (i, w) in enumerate(ws)
h = maximum(height[i:i+w]) + 1
height[i:i+w] .= h
append!(segments, ((i, h), (i+w, h)))
end
segments
end
ax = Axis(fig[1, 1])
ws = wins.(cards)
linesegments!(ax, stack(ws); color=:black, linewidth=14)
linesegments!(ax, stack(ws); color=ws, colormap=:deep, linewidth=10)
save("docs/fig/day04-ncards.svg", fig)
Tests
#test
@testset "day 4" begin
using AOC2023.Day04: card_p, score, play, play2
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"
]
cards = input .|> (first ∘ card_p)
@test cards .|> score == [8, 2, 2, 1, 0, 0]
@test 1:6 .|> play(cards) |> sum == 30
@test cards |> play2 |> sum == 30
end