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

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

register('advent.compat', function return require Module:User:Cscott/compat end)

register('day10', function(myrequire) -- DAY 10 -- local l = myrequire('llpeg') local compat = myrequire('advent.compat')

-- PARSING -- local Tile = {} Tile.__index = Tile function Tile:new(args) return setmetatable(args, self) end function Tile:p return l.P(self.c) * l.Cc(self) end function Tile:__tostring return self.c end

local NS = Tile:new{c="|",n=true,s=true} local EW = Tile:new{c="-",e=true,w=true} local NE = Tile:new{c="L",n=true,e=true} local NW = Tile:new{c="J",n=true,w=true} local SW = Tile:new{c="7",s=true,w=true} local SE = Tile:new{c="F",s=true,e=true} local GND = Tile:new{c="."} local START = Tile:new{c="S",start=true}

local nl = l.P"\n"

local patt = l.P{  "Graph", Graph = l.Ct( l.V"Row" * (nl^1 * l.V"Row")^0 * nl^0) * -1, Row = l.Ct( l.V"Tile"^1 ), Tile = NS:p + EW:p + NE:p + NW:p + SW:p + SE:p + GND:p + START:p, }

local Graph = {} Graph.__index = Graph

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

-- Part 1 --

function Graph:new(data) return setmetatable({ data=data }, self) end

function Graph:at(row,col,default) return (self.data[row] or {})[col] or default end

function Graph:set(row,col,val) if self.data == nil then self.data = {} end if self.data[row] == nil then self.data[row] = {} end self.data[row][col] = val end

function Graph:rowN return #(self.data) end

function Graph:colN return #(self.data[1]) end

function Graph:print for r,row in ipairs(self.data) do     for c,val in ipairs(row) do         if val == nil then val = " " end io.write(tostring(val)) end io.write("\n") end end

function Graph:find_start for r,row in ipairs(self.data) do     for c,val in ipairs(row) do         if val.start then return r,c end end end error("start not found") end

function Graph:fix_start local row,col = self:find_start local start = self:at(row,col) if self:at(row-1, col, {}).s then start.n = true end if self:at(row+1, col, {}).n then start.s = true end if self:at(row, col-1, {}).e then start.w = true end if self:at(row, col+1, {}).w then start.e = true end return row,col end

function flood_fill(graph) local r,c = graph:fix_start local fill = Graph:new fill:set(r,c,0) local todo = {} table.insert(todo, {r,c}) table.insert(todo, {r,c}) local i=0 local maxfill = 0 while i < #todo do     i = i + 1 local r,c = compat.unpack(todo[i]) local tile = graph:at(r,c) local fillval = fill:at(r,c) local nr,nc --print("fillval",fillval,"at",r,c) --print(inspect(tile)) if tile.n and fill:at(r-1, c) == nil then nr,nc = r-1,c elseif tile.s and fill:at(r+1, c) == nil then nr,nc = r+1,c elseif tile.e and fill:at(r,c+1) == nil then nr,nc = r,c+1 elseif tile.w and fill:at(r,c-1) == nil then nr,nc = r,c-1 else return maxfill,fill -- no more to fill! end fill:set(nr,nc,fillval+1) table.insert(todo,{nr,nc}) maxfill = math.max(maxfill, fillval+1) end end

function part1(source) local graph = parse(source) --graph:print local ans,_ = flood_fill(graph) return ans end

-- Part 2 --

function compute_inner(g, fill, nrows, ncols) local inner = false local sum = 0 for r=1,nrows do     inner = false for c=1,ncols do        local is_loop = (fill:at(r,c) ~= nil) if is_loop then -- we shoot infintesimally south of the midpoint, such that -- these count as crossing: |7F (south is true) -- and these do not:       -LJ  (south is false) if g:at(r,c).s then inner = not inner end elseif inner then -- print(r,c) sum = sum + 1 end end end return sum end

function part2(source) local graph = parse(source) --graph:print local _,fill = flood_fill(graph) local ans = compute_inner(graph, fill, graph:rowN, graph:colN) return ans end

-- CLI: local source = io.input("day10.input"):read("a") print(part1(source)) print(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) 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('day10') end)