Jump to content

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

From Wikipedia, the free encyclopedia
return (function()
local builders = {}
local function register(name, f)
  builders[name] = f
end
register('llpeg', function() return require [[Module:User:Cscott/llpeg]] 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[^>]*>\n?", "", 1)
      source = source:gsub("</syntaxhighlight[^>]*>%s*$", "", 1)
      return func(source, frame.args[2], frame.args[3])
    end
end

return {
  read_wiki_input = read_wiki_input,
}

end)

register('pqueue', function(myrequire)
--[[  Priority Queue implemented in lua, based on a binary heap.
Copyright (C) 2017 Lucas de Morais Siqueira <lucas.morais.siqueira@gmail.com>
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('day25', function(myrequire)
--[[ DAY 25 ]]--
local l = myrequire('llpeg')
local read_wiki_input = myrequire('util').read_wiki_input
local PriorityQueue = myrequire('pqueue')

--[[ PARSING ]]--
local nl = l.P"\n"
local SKIP = l.S" \t"^0

local patt = l.P{
   "Wiring",
   Wiring = l.Ct( l.V"Component" * (nl * l.V"Component")^0 * nl^0) * -1,
   Component = l.Ct( l.Cg(l.V"Name", "name") * SKIP * l.P":" * SKIP * l.Cg(l.V"ConnectionList", "connections")),
   ConnectionList = l.Ct( l.V"Name" * (l.P" " * SKIP * l.V"Name")^0 ),
   Name = l.C( l.R"az"^1 ),
}

local Node = {}
Node.__index = Node
function Node:new(p)
  return setmetatable(p or {}, self)
end
function Node:addEdge(n)
  for _,e in ipairs(self.edges) do
    if e == n then return end
  end
  table.insert(self.edges, n)
end

local function parse(source)
   local ast, errlabel, pos = patt:match(source)
   if not ast then
      error(string.format("Error at pos %s label '%s'", pos, errlabel))
   end
   -- postprocess
   local graph = {}
   -- first create all nodes
   for _,n in ipairs(ast) do
     graph[n.name] = Node:new{ name=n.name, edges={} }
     for _,n2 in ipairs(n.connections) do
       graph[n2] = Node:new{ name=n2, edges={} }
     end
   end
   -- now create all edges
   for _,n in ipairs(ast) do
     for _,n2 in ipairs(n.connections) do
       graph[n.name]:addEdge(graph[n2])
       graph[n2]:addEdge(graph[n.name])
     end
   end
   return graph
end

local function sortedKeys(tbl)
   local sorted = {}
   for k,_ in pairs(tbl) do
      table.insert(sorted, k)
   end
   table.sort(sorted)
   return sorted
end

local function shortestPath(from, to, checkEdge)
  local seen = {}
  local Q = PriorityQueue()
  Q:put({node=from,prev=nil,len=0}, 0)
  while not Q:empty() do
    local item = Q:pop()
    if item.node == to then return item end
    if seen[item.node] == nil then
      seen[item.node] = true
      for _,next in ipairs(item.node.edges) do
        if seen[next] == nil then
          if checkEdge == nil or checkEdge(item.node, next) then
            Q:put({node=next,prev=item,len=item.len+1},item.len+1)
          end
        end
      end
    end
  end
  return nil -- no path found
end

local function part1(source)
  --mw.log(source)
  local graph = parse(source)
  local nodes = sortedKeys(graph)
  -- pick a node arbitrarily
  local n = graph[nodes[1]]
  local clique1, clique2 = { n }, {}
  -- try to make paths to every other node in the graph.  either:
  -- 1) there are more than 3 possible paths => this node is in the same
  --    component as n
  -- 2) there are only 3 possible paths => this node is in the "other"
  --    component
  for i=2,#nodes do
    local n2 = graph[nodes[i]]
    local removedEdges = {}
    local function makeKey(a, b) return a.name .. '-' .. b.name end
    local noPathFound = false
    for i=1,4 do
      local path = shortestPath(n, n2, function(a, b)
                                  return removedEdges[makeKey(a,b)] == nil
      end)
      if path == nil then
        noPathFound = true
        break
      end
      -- add this path to the list of removed edges
      while path.prev ~= nil do
        removedEdges[makeKey(path.node, path.prev.node)] = true
        removedEdges[makeKey(path.prev.node, path.node)] = true
        path = path.prev
      end
    end
    if noPathFound then
      table.insert(clique2, n2)
    else
      table.insert(clique1, n2)
    end
  end
  --print(#clique1, #clique2)
  return #clique1 * #clique2
end

--[[ CLI ] ]--
local do_part1 = false
local use_example = false

local filename
if use_example then
  filename = "day25.example"
else
  filename = "day25.input"
end
local source = io.input(filename):read("*a")
print(part1(source))
--[ [ END CLI ]]--

return {
  part1 = read_wiki_input(part1),
}

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