bliss

KISS in Lua
git clone git://bvnf.space/bliss.git
Log | Files | Refs | README | LICENSE

tsort.lua (2125B)


      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
--- Topological sorting.
-- This module is not included in init.lua, but used locally by pkg.lua.
-- @module bliss.tsort

--- @type tsort
local tsort = {}

-- Returns a table of reverse deps
local function reverse(input)
    local reversed = {}
    for k,v in pairs(input) do
        for _,w in ipairs(v) do
            reversed[w] = reversed[w] or {}
            table.insert(reversed[w], k)
        end
    end
    return reversed
end

local function find_cycle(revlinks, start)
    local ret = {[start] = 1}
    local old

    local t = revlinks[start][1]
    while t and not ret[t] do
        ret[t] = 1
        old = t
        t = revlinks[t][1]
    end
    return t, old
end

--- Get a new tsort object.
-- @treturn tsort
function tsort.new()
    return setmetatable({nodes={}}, {__index = tsort})
end

--- Add a node.
-- @tparam string node
-- @tparam table deps array of all the deps for node. Calling add for the same node a second time overwrites, not appends.
function tsort:add(node, deps)
    self.nodes[node] = deps
end

--- Sort the nodes.
-- @treturn table sorted array of nodes
function tsort:sort()
    if not self.nodes then return nil end
    local L = {}
    local reversed = reverse(self.nodes)

    -- find nodes with no incoming edges
    local S = {}
    for k in pairs(self.nodes) do
        if not reversed[k] or #reversed[k] == 0 then
            table.insert(S, k)
        end
    end

    while #S ~= 0 do
        local n = table.remove(S, 1)
        table.insert(L, n)
        for _,v in ipairs(self.nodes[n] or {}) do
            -- remove edge n -> v
            for i,j in ipairs(reversed[v]) do
                if j == n then
                    table.remove(reversed[v], i)
                end
            end
            if not reversed[v] or #reversed[v] == 0 then
                table.insert(S, v)
                reversed[v] = nil
            end
        end
    end

    for k,v in pairs(reversed) do
        if #v ~= 0 then
            -- cycle detected
            local x,y = find_cycle(reversed, k)
            return nil, x, y
        end
    end
    return L
end

return tsort