gears: Add a new sort module
The first use case is a topological sort to handle dependency graphs Closes #2159
This commit is contained in:
parent
fb0ccc3220
commit
4e0915674d
|
@ -21,6 +21,7 @@ return
|
|||
math = require("gears.math");
|
||||
table = require("gears.table");
|
||||
string = require("gears.string");
|
||||
sort = require("gears.sort");
|
||||
filesystem = require("gears.filesystem");
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
---------------------------------------------------------------------------
|
||||
--- Extra sorting algorithms.
|
||||
--
|
||||
-- @module gears.sort
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
topological = require("gears.sort.topological")
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
---------------------------------------------------------------------------
|
||||
--- Extra sorting algorithms.
|
||||
--
|
||||
-- @submodule gears.sort
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
local tsort = {}
|
||||
local gtable = require("gears.table")
|
||||
|
||||
local mt = { __index = tsort }
|
||||
|
||||
local function add_node(self, node)
|
||||
if not self._edges[node] then
|
||||
self._edges[node] = {}
|
||||
end
|
||||
end
|
||||
|
||||
--- Ensure that `node` appears after all `dependencies`.
|
||||
-- @param node The node that edges are added to.
|
||||
-- @tparam table dependencies List of nodes that have to appear before `node`.
|
||||
function tsort:append(node, dependencies)
|
||||
add_node(self, node)
|
||||
for _, dep in ipairs(dependencies) do
|
||||
add_node(self, dep)
|
||||
self._edges[node][dep] = true
|
||||
end
|
||||
end
|
||||
|
||||
--- Ensure that `node` appears before all `subordinates`.
|
||||
-- @param node The node that edges are added to.
|
||||
-- @tparam table subordinates List of nodes that have to appear after `node`.
|
||||
function tsort:prepend(node, subordinates)
|
||||
for _, dep in ipairs(subordinates) do
|
||||
self:append(dep, { node })
|
||||
end
|
||||
end
|
||||
|
||||
local HANDLING, DONE = 1, 2
|
||||
|
||||
local function visit(result, self, state, node)
|
||||
if state[node] == DONE then
|
||||
-- This node is already in the output
|
||||
return
|
||||
end
|
||||
if state[node] == HANDLING then
|
||||
-- We are handling this node already and managed to visit it again
|
||||
-- from itself. Thus, there must be a loop.
|
||||
result.BAD = node
|
||||
return true
|
||||
end
|
||||
|
||||
state[node] = HANDLING
|
||||
-- Before this node, all nodes that it depends on must appear
|
||||
for dep in pairs(self._edges[node]) do
|
||||
if visit(result, self, state, dep) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
state[node] = DONE
|
||||
table.insert(result, node)
|
||||
end
|
||||
|
||||
--- Create a copy of this topological sort.
|
||||
-- This is useful to backup it before adding elements that can potentially
|
||||
-- have circular dependencies and thus render the original useless.
|
||||
function tsort:clone()
|
||||
local new = tsort.topological()
|
||||
|
||||
-- Disable deep copy as the sorted values may be objects or tables
|
||||
new._edges = gtable.clone(self._edges, false)
|
||||
|
||||
return new
|
||||
end
|
||||
|
||||
--- Remove a node from the topological map.
|
||||
--
|
||||
-- @param node The node
|
||||
function tsort:remove(node)
|
||||
self._edges[node] = nil
|
||||
for _, deps in pairs(self._edges) do
|
||||
deps[node] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Try to sort the nodes.
|
||||
-- @treturn[1] table A sorted list of nodes
|
||||
-- @treturn[2] nil
|
||||
-- @return[2] A node around which a loop exists
|
||||
function tsort:sort()
|
||||
local result, state = {}, {}
|
||||
for node in pairs(self._edges) do
|
||||
if visit(result, self, state, node) then
|
||||
return nil, result.BAD
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
--- A topological sorting class.
|
||||
--
|
||||
-- The object returned by this function allows to create simple dependency
|
||||
-- graphs. It can be used for decision making or ordering of complex sequences.
|
||||
--
|
||||
--
|
||||
--@DOC_text_gears_sort_topological_EXAMPLE@
|
||||
--
|
||||
-- @function gears.sort.topological
|
||||
|
||||
function tsort.topological()
|
||||
return setmetatable({
|
||||
_edges = {},
|
||||
}, mt)
|
||||
end
|
||||
|
||||
return setmetatable(tsort, {__call = function(_, ...)
|
||||
return tsort.topological(...)
|
||||
end})
|
Loading…
Reference in New Issue