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

return (function()
local builders = {}
local function register(name, f)
  builders[name] = f
end
register('llpeg', function() return require [[Module:User:Cscott/llpeg]] end)

register('day15', function(myrequire)
--[[ DAY 15 ]]--
local lpeg = myrequire('llpeg')

--[[ PARSING ]]--
function split (s, sep)
  sep = lpeg.P(sep)
  local elem = lpeg.C((1 - sep)^0)
  local p = lpeg.Ct(elem * (sep * elem)^0)   -- make a table capture
  return lpeg.match(p, s)
end

function parse1(s)
   s = s:gsub("\n", "")
   return split(s, ",")
end

function hash(s)
   local currentValue = 0
   for _,ascii in ipairs{string.byte(s, 1, #s)} do
      currentValue = currentValue + ascii
      currentValue = currentValue * 17
      currentValue = currentValue % 256
   end
   return currentValue
end


function part1(s)
   local sum = 0
   for _,ss in ipairs(parse1(s)) do
      sum = sum + hash(ss)
   end
   return sum
end

--[[ PART 2 ]]--
local l = lpeg
local nl = l.P"\n"
local patt = l.P{
   l.Ct( l.V"Op" * (l.P"," * l.V"Op")^0 * nl^0 ) * -1,
   Label = l.Cg( l.R"az"^1, "label" ) * l.Cg( l.Cb("label") / hash, "hash"),
   Number = (l.R"09"^1) / tonumber,
   OpDash = l.V"Label" * l.P"-" * l.Cg(l.Cc(true), "dash"),
   OpEq = l.V"Label" * l.P"=" * l.Cg( l.V"Number", "focalLen" ),
   Op = l.Ct( l.V"OpDash" + l.V"OpEq" ),
}

function parse2(source)
   source = source:gsub("\n", "")
   --print(inspect(source))
   local ast, errlabel, pos = patt:match(source)
   if not ast then
      error(string.format("Error at pos %s label '%s'", pos, errlabel))
   end
   --print('Parsed with success!')
   --print(inspect(ast))
   return ast
end

local Lens = {}
Lens.__index = Lens
function Lens:new(props)
   return setmetatable(props or {}, self)
end

local Box = {}
Box.__index = Box
function Box:new()
   return setmetatable({ lensList={}, lensMap={} }, self)
end
function Box:get(label)
   return self.lensMap[label]
end
function Box:addLens(label, lens)
   table.insert(self.lensList, lens)
   lens.pos = #(self.lensList)
   self.lensMap[label] = lens
end
function Box:remove(label)
   local old = self:get(label)
   old.focalLen = nil -- tombstone
   self.lensMap[label] = nil -- remove
end
function Box:sum(mult)
   local i = 1
   local sum = 0
   for _,l in ipairs(self.lensList) do
      if l.focalLen ~= nil then
         sum = sum + (mult * i * l.focalLen)
         i = i + 1
      end
   end
   return sum
end

local Hashmap = {}
Hashmap.__index = Hashmap;

function Hashmap:new(s)
   return setmetatable(s or {}, self)
end

function Hashmap:getBox(i)
   local box = self[i+1]
   if box == nil then
      box = Box:new()
      self[i+1] = box
   end
   return box
end

function Hashmap:execute(op)
   if op.dash then
      self:executeDash(op)
   else
      self:executeEq(op)
   end
end

function Hashmap:executeDash(op)
   local box = self:getBox(op.hash)
   local old = box:get(op.label)
   if old ~= nil then
      box:remove(op.label)
   end
end

function Hashmap:executeEq(op)
   local box = self:getBox(op.hash)
   local old = box:get(op.label)
   if old ~= nil then
      -- already a lens with this label, replace it
      old.focalLen = op.focalLen
   else
      local lens = Lens:new{focalLen = op.focalLen}
      box:addLens(op.label, lens)
   end
end

function Hashmap:sum()
   local sum = 0
   for i=0,255 do
      local box = self:getBox(i)
      sum = sum + box:sum(i+1)
   end
   return sum
end

function part2(source)
   local h = Hashmap:new()
   local ops = parse2(source)
   for i=1,#ops do
      h:execute(ops[i])
   end
   return h:sum()
end

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

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, tonumber(frame.args[2]))
   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('day15')
end)()