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

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

register('day13', function(myrequire)
--[[ DAY 13 ]]--
local l = myrequire('llpeg')

--[[ PARSING ]]--
local Node = {}
Node.__index = Node
function Node:new(args)
   return setmetatable(args, self)
end
function Node:__tostring()
   if self.rock then return "#" end
   return "."
end

local nl = l.P"\n"

function make_node(s)
   if s == "#" then return Node:new{rock=true} end
   return Node:new{ash=true}
end

local Map = {}
Map.__index = Map

function Map:nRows()
   return #(self.data)
end

function Map:nCols()
   return #(self.data[1])
end

function Map:__tostring()
   local result = {}
   for r,row in ipairs(self.data) do
      for c,node in ipairs(row) do
         table.insert(result, tostring(node))
      end
      table.insert(result, "\n")
   end
   return table.concat(result)
end

function Map:try_vertical_mirror(col, smudges) -- # of columns to its left
   local s = 0
   for r,row in ipairs(self.data) do
      local i = 0
      repeat
         local col1,col2 = col-i,col+1+i
         if row[col1].ash ~= row[col2].ash then
            s = s + 1
            if s > smudges then return false end -- early return
         end
         i = i + 1
      until col1 == 1 or col2 == #row
   end
   return (s == smudges)
end

function Map:try_horizontal_mirror(row, smudges) -- # of columns to its left
   local s = 0
   for c = 1,self:nCols() do
      local i = 0
      repeat
         local row1,row2 = row-i,row+1+i
         if self.data[row1][c].ash ~= self.data[row2][c].ash then
            s = s + 1
            if s > smudges then return false end -- early return
         end
         i = i + 1
      until row1 == 1 or row2 == #(self.data)
   end
   return (s == smudges)
end

function Map:score(smudges)
   for i=1,self:nRows()-1 do
      if self:try_horizontal_mirror(i, smudges) then
         return 100*i
      end
   end
   for i=1,self:nCols()-1 do
      if self:try_vertical_mirror(i, smudges) then
         return i
      end
   end
   error("no axis of reflection found")
end

function make_map(g)
   return setmetatable({ data=g }, Map)
end

local patt = l.P{
   "MapList",
   MapList = l.Ct( l.V"Map" * (nl * nl * l.V"Map")^0 * nl^0) * -1,
   Map = l.Ct( l.V"Row" * (nl * l.V"Row")^0 ) / make_map,
   Row = l.Ct( l.V"Node"^1 ),
   Node = l.S"#." / make_node,
}


function parse(source)
   --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 Graph:new(ast)
   return ast
end

--[[ part 1 ]]--

function part1(source)
   local maps = parse(source)
   local sum = 0
   for i=1,#maps do
      local score = maps[i]:score(0)
      --print(maps[i])
      --print(score)
      --print()
      sum = sum + score
   end
   return sum
end

--[[ part 2 ]]--

function part2(source)
   local maps = parse(source)
   local sum = 0
   for i=1,#maps do
      local score = maps[i]:score(1)
      --print(maps[i])
      --print(score)
      --print()
      sum = sum + score
   end
   return sum
end

--[[ CLI
local source = io.input("day13.input"):read("a")
--print(inspect(parse(source)))
print('Part 1 Sum:', part1(source))
print('Part 2 Sum:', part2(source))
]]--

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('day13')
end)()