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

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('advent.compat', function() return require [[Module:User:Cscott/compat]] end)

register('bignum', function(myrequire)
local compat = myrequire('advent.compat')

-- poor man's bignum library
local RADIX = 1000 -- power of 10 to make printout easier

local BigNum = {}
BigNum.__index = BigNum
function BigNum:new(n)
  return setmetatable( {n or 0}, self):normalize()
end
function BigNum:__tostring()
  local result = {}
  local first = true
  for i=#self,1,-1 do
    local n = self[i]
    if n ~= 0 or not first then
      local s = tostring(n)
      if first then
        first = false
      else
        while #s < 3 do s = '0' .. s end
      end
      table.insert(result, s)
    end
  end
  if #result == 0 then return "0" end
  return table.concat(result)
end
function BigNum:normalize()
  local i = 1
  while self[i] ~= nil do
    if self[i] >= 1000 then
      local carry = math.floor(self[i] / 1000)
      self[i] = self[i] % 1000
      self[i+1] = (self[i+1] or 0) + carry
    end
    i = i + 1
  end
  return self
end
function BigNum:copy()
  local r = BigNum:new()
  for i=1,#self do
    r[i] = self[i]
  end
  return r
end
function BigNum.__add(a, b)
  if type(a) == 'number' then
    a,b = b,a
  end
  local r = a:copy()
  if type(b) == 'number' then
    r[1] = (r[1] or 0) + b
  else
    for i=1,#b do
      r[i] = (r[i] or 0) + b[i]
    end
  end
  return r:normalize()
end
function BigNum.__mul(a, b)
  if type(a) == 'number' then
    a,b = b,a
  end
  local r = BigNum:new()
  if type(b) == 'number' then
    for i=1,#a do
      r[i] = a[i] * b
    end
    return r:normalize()
  end
  for i=1,#a do
    for j=1,#b do
      local prod = a[i] * b[j]
      r[i+j-1] = (r[i+j-1] or 0) + prod
    end
    r:normalize()
  end
  return r
end

return BigNum

end)

register('util', function(myrequire)
local function read_wiki_input(func)
    return function (frame, ...)
      if type(frame)=='string' then
        frame = { args = { frame, ... } }
      end
      local title = mw.title.new(frame.args[1])
      local source = title:getContent()
      if source == nil then
        error("Can't find title " .. tostring(title))
      end
      source = source:gsub("^%s*<syntaxhighlight[^>]*>", "", 1)
      source = source:gsub("</syntaxhighlight[^>]*>%s*$", "", 1)
      return func(source)
    end
end

return {
  read_wiki_input = read_wiki_input,
}

end)

register('day19', function(myrequire)
--[[ DAY 19 ]]--

local lpegrex = myrequire('llpeg.lpegrex')
local compat = myrequire('advent.compat')
local BigNum = myrequire('bignum')
local read_wiki_input = myrequire('util').read_wiki_input

--[[ PARSING ]]--

local patt = lpegrex.compile([[
Puzzle <== nl* {:workflows: Workflows :} nl+ {:parts: Parts :} nl*
Workflows <-| Workflow (nl Workflow)*
Workflow <-| Name `{` (Rule `,`)* FinalRule `}`
Name <-- {:name: %w+ :} SKIP
Rule <== Prop Op Val `:` Dest
FinalRule:Rule <== Dest
Prop <-- {:prop: %w+ :}
Op <-- {:op: `<` -> 'lt' / `>` -> 'gt' :}
Val <-- {:val: Number :}
Dest <-- {:dest: %w+ :}
Number <- (%d+ -> tonumber) SKIP

Parts <-| Part (nl Part)*
Part <== `{` Rating ( `,` Rating )* `}`
Rating <-| Prop `=` Val

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))
   -- turn the lists into maps
   local workflowMap = {}
   for _,v in ipairs(ast.workflows) do
     workflowMap[v.name] = v
   end
   ast.workflows = workflowMap

   local partList = {}
   for i,part in ipairs(ast.parts) do
     local partProps = {}
     for _,v in ipairs(part) do
       partProps[v.prop] = v.val
     end
     partProps.id = i
     partList[i] = partProps
   end
   ast.parts = partList
   return ast
end

--[[ PART 1 ]]--

local function processPart(workflows, name, part)
  if name == 'R' then return false end -- rejected
  if name == 'A' then return true end -- accepted!
  local w = workflows[name]
  for _,rule in ipairs(w) do
    -- final rule: automatically dispatch
    if rule.op == nil then
      return processPart(workflows, rule.dest, part)
    end
    -- otherwise, process 'lt' rule
    if rule.op == 'lt' and part[rule.prop] < rule.val then
      return processPart(workflows, rule.dest, part)
    end
    if rule.op == 'gt' and part[rule.prop] > rule.val then
      return processPart(workflows, rule.dest, part)
    end
  end
  error("should not reach here")
end

local function part1(source)
  local puzzle = parse(source)
  local sum = 0
  for _,part in ipairs(puzzle.parts) do
    local accept = processPart(puzzle.workflows, 'in', part)
    if accept then
      sum = sum + part.x + part.m + part.a + part.s
    end
  end
  return sum
end

--[[ PART 2 ]]--

local Range = {}
Range.__index = Range
function Range:new(p)
  return setmetatable(p or { min=1, max=4000 }, self)
end
-- returns lt, ge split
function Range:split(val)
  if val <= self.min then
    return nil, self -- entire self is above the split
  elseif val > self.max then
    return self, nil -- entire self is below the split
  else
    local lt = Range:new{ min=self.min, max=val-1 }
    local ge = Range:new{ min=val, max=self.max }
    return lt, ge
  end
end
function Range:__len()
  -- number of values in the range
  return 1 + self.max - self.min
end
function Range:__tostring()
  return string.format("[%d,%d]", self.min, self.max)
end

local XMAS = {}
XMAS.__index = XMAS
function XMAS:new(p)
  return setmetatable(p or {
      x=Range:new(), m=Range:new(), a=Range:new(), s=Range:new(),
  }, self)
end
function XMAS:__len()
  -- number of values in the XMAS
  return compat.len(self.x) * compat.len(self.m) * compat.len(self.a) * compat.len(self.s)
end
function XMAS:biglen()
  return BigNum:new(1) * compat.len(self.x) * compat.len(self.m) * compat.len(self.a) * compat.len(self.s)
end
function XMAS:__tostring()
  return string.format("{x=%s,m=%s,a=%s,s=%s}", self.x, self.m, self.a, self.s)
end
function XMAS:replace(prop, newRange)
  local xmas = XMAS:new{ x=self.x, m=self.m, a=self.a, s=self.s }
  xmas[prop] = newRange
  return xmas
end
-- returns lt, ge split
function XMAS:split(prop, val)
  local r = self[prop]
  local lt, ge = self[prop]:split(val)
  if lt ~= nil then lt = self:replace(prop, lt) end
  if ge ~= nil then ge = self:replace(prop, ge) end
  return lt, ge
end

local function processXMAS(workflows, name, xmas, accepted, rejected)
  if name == 'R' then -- rejected
    if rejected ~= nil then
      table.insert(rejected, xmas)
    end
    return
  end
  if name == 'A' then -- accepted!
    table.insert(accepted, xmas)
    return
  end
  local w = workflows[name]
  for _,rule in ipairs(w) do
    -- final rule: automatically dispatch
    if rule.op == nil then
      return processXMAS(workflows, rule.dest, xmas, accepted, rejected)
    -- otherwise, process 'lt' rule
    elseif rule.op == 'lt' then
      local lt,ge = xmas:split(rule.prop, rule.val)
      -- recurse to handle the part which matches this rule
      processXMAS(workflows, rule.dest, lt, accepted, rejected)
      -- continue to handle the part which doesn't match
      xmas = ge
    elseif rule.op == 'gt' then
      local le,gt = xmas:split(rule.prop, rule.val + 1)
      -- recurse to handle the part which matches this rule
      processXMAS(workflows, rule.dest, gt, accepted, rejected)
      -- continue to handle the part which doesn't match
      xmas = le
    end
  end
  error("should not reach here")
end

local function part2(source)
  local puzzle = parse(source)
  local accepted = {}
  processXMAS(puzzle.workflows, 'in', XMAS:new(), accepted)

  local sum = BigNum:new(0)
  for _,v in ipairs(accepted) do
    --print(tostring(v), compat.len(v))
    sum = sum + v:biglen()
  end
  return sum
end



--[[ CLI ] ]--
local source = io.input("day19.input"):read("*a")
print('Sum:', part1(source))
print('Combinations:', part2(source))
--[ [ END CLI ]]--

return {
  part1 = read_wiki_input(part1),
  part2 = read_wiki_input(part2),
}

end)

local modules = {}
modules['bit32'] = require('bit32')
modules['string'] = require('string')
modules['strict'] = {}
modules['table'] = require('table')
local function myrequire(name)
  if modules[name] == nil then
    modules[name] = true
    modules[name] = (builders[name])(myrequire)
  end
  return modules[name]
end
return myrequire('day19')
end)()

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.