Module:User:Cscott/Advent Of Code 2023/Day 7

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( 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)