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

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

register('pqueue', function(myrequire) -- Priority Queue implemented in lua, based on a binary heap. Copyright (C) 2017 Lucas de Morais Siqueira  License: zlib  This software is provided 'as-is', without any express or implied  warranty. In no event will the authors be held liable for any damages  arising from the use of this software.  Permission is granted to anyone to use this software for any purpose,  including commercial applications, and to alter it and redistribute it  freely, subject to the following restrictions:  1. The origin of this software must not be misrepresented; you must not     claim that you wrote the original software. If you use this software     in a product, an acknowledgement in the product documentation would be     appreciated but is not required.  2. Altered source versions must be plainly marked as such, and must not be     misrepresented as being the original software.  3. This notice may not be removed or altered from any source distribution. -- -- modified by xxopxe@gmail.com

local floor = math.floor

local PriorityQueue = {} PriorityQueue.__index = PriorityQueue

setmetatable( PriorityQueue,  {    __call = function       local new = {}      setmetatable(new, PriorityQueue)      new:initialize      return new    end  } )

function PriorityQueue:initialize -- Initialization.    Example:        PriorityQueue = require "priority_queue"        pq = PriorityQueue    -- self.heap_val = {} self.heap_pri = {} self.current_size = 0 end

function PriorityQueue:empty return self.current_size == 0 end

function PriorityQueue:size return self.current_size end

function PriorityQueue:swim -- Swim up on the tree and fix the order heap property. local heap_val = self.heap_val local heap_pri = self.heap_pri local floor = floor local i = self.current_size

while floor(i / 2) > 0 do   local half = floor(i / 2) if heap_pri[i] < heap_pri[half] then heap_val[i], heap_val[half] = heap_val[half], heap_val[i] heap_pri[i], heap_pri[half] = heap_pri[half], heap_pri[i] end i = half end end

function PriorityQueue:put(v, p) -- Put an item on the queue.    Args:        v: the item to be stored        p(number): the priority of the item    -- -- self.current_size = self.current_size + 1 self.heap_val[self.current_size] = v self.heap_pri[self.current_size] = p  self:swim end

function PriorityQueue:sink -- Sink down on the tree and fix the order heap property. local size = self.current_size local heap_val = self.heap_val local heap_pri = self.heap_pri local i = 1

while (i * 2) <= size do   local mc = self:min_child(i) if heap_pri[i] > heap_pri[mc] then heap_val[i], heap_val[mc] = heap_val[mc], heap_val[i] heap_pri[i], heap_pri[mc] = heap_pri[mc], heap_pri[i] end i = mc end end

function PriorityQueue:min_child(i) if (i * 2) + 1 > self.current_size then return i * 2 else if self.heap_pri[i * 2] < self.heap_pri[i * 2 + 1] then return i * 2 else return i * 2 + 1 end end end

function PriorityQueue:pop -- Remove and return the top priority item local heap_val = self.heap_val local heap_pri = self.heap_pri local retval, retprio = heap_val[1], heap_pri[1] heap_val[1], heap_pri[1] = heap_val[self.current_size], heap_pri[self.current_size] heap_val[self.current_size], heap_pri[self.current_size] = nil, nil self.current_size = self.current_size - 1 self:sink return retval, retprio end

function PriorityQueue:peek -- return the top priority item return self.heap_val[1], self.heap_pri[1] end

return PriorityQueue

end)

register('day11', function(myrequire) -- DAY 11 -- local l = myrequire('llpeg') local PriorityQueue = myrequire('pqueue')

-- PARSING -- local Parsec = {} Parsec.__index = Parsec function Parsec:new(args) return setmetatable(args, self) end function Parsec:__tostring if self.galaxy then if self.id == nil then return "#" end if self.id < 10 then return tostring(self.id) end if self.id < 36 then return string.char(65 + self.id - 10) end return "#" -- ran out of labels end return "." end

local nl = l.P"\n"

function make_parsec(s) if s == "#" then return Parsec:new{galaxy=true, xlen=1, ylen=1} end return Parsec:new{xlen=1, ylen=1} end

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"Parsec"^1 ), Parsec = l.S"#." / make_parsec, }

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:number local galaxies = {} local num = 1 for r,row in ipairs(self.data) do     for c,sp in ipairs(row) do         if sp.galaxy then sp.id = num num = num + 1 table.insert(galaxies, sp) end end end return galaxies end

function Graph:expand(amt) if amt == nil then amt = 2 end -- first rows for r=1,self:rowN do     local saw_galaxy = false for c=1,self:colN do        if self:at(r, c).galaxy then saw_galaxy = true break end end if not saw_galaxy then -- if we didn't see a galaxy, expand space for c = 1,self:colN do           self.data[r][c].ylen = amt end end end -- now columns for c=1,self:colN do     local saw_galaxy = false for r=1,self:rowN do        if self:at(r, c).galaxy then saw_galaxy = true break end end if not saw_galaxy then -- if we didn't see a galaxy, expand space for r=1,self:rowN do           self.data[r][c].xlen = amt end end end end

function Graph:link for r=1,self:rowN do     for c=1,self:colN do         local sp = self:at(r,c) sp.r, sp.c = r,c if r > 1 then sp.n = self:at(r-1,c) end if c > 1 then sp.w = self:at(r,c-1) end if r < self:rowN then sp.s = self:at(r+1,c) end if c < self:colN then sp.e = self:at(r,c+1) end end end end

function Graph:number_nodes -- number all nodes local V = {} for r=1,self:rowN do     for c=1,self:colN do         local sp = self:at(r,c) table.insert(V, sp) sp.v = #V end end return V end

-- Dijkstra algorithm function Graph:shortest_path_from_node(V, galaxies, galaxy_num) local dist = {} local prev = {} local Q = PriorityQueue local seen = {} dist[galaxies[galaxy_num].v] = 0 Q:put(galaxies[galaxy_num], 0) while not Q:empty do     local u = Q:pop seen[u.v] = true local neighbor = function(neigh, weight) if neigh == nil then return end if seen[neigh.v] then return end local alt = dist[u.v] + weight if dist[neigh.v] == nil or alt < dist[neigh.v] then dist[neigh.v] = alt prev[neigh.v] = u           Q:put(neigh, dist[neigh.v]) end end neighbor(u.n, u.ylen) neighbor(u.s, u.ylen) neighbor(u.e, u.xlen) neighbor(u.w, u.xlen) end local sum = 0 for g=1,#galaxies do     local gdist = dist[galaxies[g].v]      -- print(string.format("Between galaxy %d and galaxy %d: %d", galaxy_num, g,gdist)) sum = sum + gdist end return sum end

-- Floyd-Warshall algorithm function Graph:all_pairs_shortest_path(V, galaxies) -- let dist be a |V| x |V| array of minimum distances initialzed to  -- infinity (which we'll represent as 'nil') local dist = {} for i=1,#V do     table.insert(dist, {}) end -- initialize with edge weights for r=1,self:rowN do     for c=1,self:colN do         local sp = self:at(r,c) -- dist[v][v] = 0 dist[sp.v][sp.v] = 0 if sp.n ~= nil then dist[sp.v][sp.n.v] = sp.ylen end if sp.s ~= nil then dist[sp.v][sp.s.v] = sp.ylen end if sp.e ~= nil then dist[sp.v][sp.e.v] = sp.xlen end if sp.w ~= nil then dist[sp.v][sp.w.v] = sp.xlen end end end -- ok, the big O(N^3) step for k=1,#V do     for i = 1,#V do         for j = 1,#V do            -- if distances are nil they are infinity if dist[i][k] ~= nil and dist[k][j] ~= nil then if dist[i][j] == nil or                 dist[i][j] > (dist[i][k] + dist[k][j]) then dist[i][j] = dist[i][k] + dist[k][j] end end end end end -- ok, all pairs shortest paths! local sum = 0 for i=1,#galaxies do     for j=i+1,#galaxies do         local iv = galaxies[i].v         local jv = galaxies[j].v         local dist = dist[iv][jv] sum = sum + dist -- print(string.format("Between galaxy %d and galaxy %d: %d", i, j, dist)) end end return sum end

function part12(source, amt) local graph = parse(source) --graph:print graph:expand(amt) local galaxies = graph:number graph:link -- graph:print -- print(graph:rowN,'x',graph:colN) local V = graph:number_nodes --return graph:all_pairs_shortest_path(V, galaxies) local sum = 0 for g=1,#galaxies do     sum = sum + graph:shortest_path_from_node(V, galaxies, g)   end -- we double counted each pair, so: return math.floor(sum/2) end

function part1(source) return part12(source, 2) end function part2(source, amt) return part12(source, amt) end

-- CLI: local source = io.input("day11.input"):read("a") print('Sum:', part1(source)) print('Sum:', part2(source, 1000000)) --

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