Module:User:Cscott/Advent Of Code 2023/Day 7
Appearance
return (function()
local builders = {}
local function register(name, f)
builders[name] = f
end
register('llpeg.lpegrex', function() return require [[Module:User:Cscott/lpegrex]] end)
register('llpeg', function() return require [[Module:User:Cscott/llpeg]] end)
register('day7', function(myrequire)
--[[ DAY 7 ]]--
local lpegrex = myrequire('llpeg.lpegrex')
local l = myrequire('llpeg')
--[[ PARSING ]]--
local patt = lpegrex.compile([[
Lines <-- {| nl* HandBid (nl+ HandBid)* nl* |}
HandBid <-- {| {:hand: Hand :} [ ]+ {:bid: Number :} |}
Hand <-- Card Card Card Card Card
Card <-- [AKQJT98765432j]
Number <-- %d+ -> tonumber
nl <-- %nl
SKIP <-- [ ]*
NAME_SUFFIX <-- [_%w]+
]])
function parse(source)
--print(inspect(source))
local ast, errlabel, pos = patt:match(source)
if not ast then
local lineno, colno, line = lpegrex.calcline(source, pos)
local colhelp = string.rep(' ', colno-1)..'^'
error('syntax error: '..lineno..':'..colno..': '..errlabel..
'\n'..line..'\n'..colhelp)
end
--print('Parsed with success!')
--print(inspect(ast))
return ast
end
--[[ PART 1 ]]--
local JOKER = 1 -- must be lower than the "2"
local card_value = {
j=JOKER, -- joker!
["2"]=2, ["3"]=3, ["4"]=4, ["5"]=5, ["6"]=6, ["7"]=7,
["8"]=8, ["9"]=9, T=10, J=11, Q=12, K=13, A=14,
}
local Kind = { -- chosen so they sort in order
HighCard = "0high",
OnePair = "1pair",
TwoPair = "2pair",
ThreeOfAKind = "3ofakind",
FullHouse = "3zfullhouse",
FourOfAKind = "4ofakind",
FiveOfAKind = "5ofakind",
}
local split_patt = l.Ct((l.P(1) / card_value)^5)
function split(hand)
-- split hand into cards, map each to value
return split_patt:match(hand)
end
function kind1(hand)
-- count duplicates
local count = {}
local max = 0
for _,v in ipairs(hand) do
count[v] = (count[v] or 0) + 1
max = math.max(max, count[v])
end
if max == 5 then return Kind.FiveOfAKind end
if max == 4 then return Kind.FourOfAKind end
-- count pairs
local pairCount = 0
for _,cnt in pairs(count) do
if cnt == 2 then pairCount = pairCount + 1 end
end
if max == 3 then
if pairCount == 1 then return Kind.FullHouse end
return Kind.ThreeOfAKind
end
if pairCount == 2 then return Kind.TwoPair end
if pairCount == 1 then return Kind.OnePair end
return Kind.HighCard
end
function compare_hands(a, b)
if a.kind ~= b.kind then return a.kind < b.kind end
-- sort by first card, then second, etc.
for i=1,5 do
if a.split[i] ~= b.split[i] then return a.split[i] < b.split[i] end
end
--print(inspect(a), inspect(b))
--error("duplicate hands")
return false
end
function winnings(source, compute_kind_func)
local hands = parse(source)
--print(inspect(hands))
-- compute the kind for every hand
for _,h in ipairs(hands) do
h.split = split(h.hand)
h.kind = compute_kind_func(h.split)
end
-- sort the hands
table.sort(hands, compare_hands)
-- sum the winnings!
local sum = 0
for rank,h in ipairs(hands) do
-- print(rank,h.hand,h.bid,h.kind)
sum = sum + rank * h.bid
end
return sum
end
--[[ Part 2 ]]--
function kind2(hand)
-- count duplicates
local count = {}
local max = 0
for _,v in ipairs(hand) do
count[v] = (count[v] or 0) + 1
max = math.max(max, count[v])
end
local jokers = count[JOKER] or 0
if max == 5 then return Kind.FiveOfAKind end
if max == 4 then
if jokers > 0 then return Kind.FiveOfAKind end
return Kind.FourOfAKind
end
-- count pairs
local pairCount = 0
for _,cnt in pairs(count) do
if cnt == 2 then pairCount = pairCount + 1 end
end
if max == 3 then
if jokers == 3 then
-- option 1: three jokers, two matching cards
if pairCount == 1 then return Kind.FiveOfAKind end
-- option 2: three jokers, two non matching cards
return Kind.FourOfAKind
elseif jokers == 2 then
-- option 3: three cards, two jokers
return Kind.FiveOfAKind
elseif jokers == 1 then
-- option 4: three cards, 1 card, 1 joker
return Kind.FourOfAKind
elseif pairCount == 1 then
-- option 5: no jokers, full house
return Kind.FullHouse
else
-- option 6: no jokers
return Kind.ThreeOfAKind
end
end
-- max <= 2
if pairCount == 2 then
if jokers == 2 then
-- two jokers, two matching cards, 1 non matching
return Kind.FourOfAKind
elseif jokers == 1 then
-- one joker, two pair
return Kind.FullHouse
else
return Kind.TwoPair
end
elseif pairCount == 1 then
if jokers > 0 then
-- one pair of jokers + one card, or one pair + 1 joker
return Kind.ThreeOfAKind
else
return Kind.OnePair
end
elseif jokers > 0 then
return Kind.OnePair
else
return Kind.HighCard
end
end
function part1(source)
return winnings(source, kind1) -- part 1
end
function part2(source)
source = source:gsub("J","j") -- jokers!
return winnings(source, kind2)
end
--[[ CLI start ] ]--
local source = io.input("day7.input"):read("a")
print(part1(source))
print(part2(source))
--[ [ CLI end ]]--
return {
part1 = function(frame)
local s = mw.title.new(frame.args[1]):getContent()
return part1(s)
end,
part2 = function(frame)
local s = mw.title.new(frame.args[1]):getContent()
return part2(s)
end,
}
end)
local modules = {}
modules['table'] = require('table')
modules['string'] = require('string')
modules['strict'] = {}
local function myrequire(name)
if modules[name] == nil then
modules[name] = true
modules[name] = (builders[name])(myrequire)
end
return modules[name]
end
return myrequire('day7')
end)()