284 lines
7.1 KiB
Lua
284 lines
7.1 KiB
Lua
Graph = Object:extend()
|
|
function Graph:init()
|
|
self.adjacency_list = {}
|
|
self.nodes = {}
|
|
self.edges = {}
|
|
self.floyd_dists = {}
|
|
end
|
|
|
|
|
|
-- Nodes can be of any type but must be unique
|
|
-- graph = Graph()
|
|
-- graph:add_node(1)
|
|
-- graph:add_node('node_2')
|
|
function Graph:add_node(node)
|
|
self.adjacency_list[node] = {}
|
|
self:_set_nodes()
|
|
end
|
|
|
|
|
|
-- Returns a node after searching for it by property, the property value must be unique among all nodes
|
|
-- graph = Graph()
|
|
-- graph:add_node({id = 1})
|
|
-- graph:add_node({id = 2})
|
|
-- graph:get_node_by_property('id', 1) -> original {id = 1} table
|
|
function Graph:get_node_by_property(key, value)
|
|
for node, _ in pairs(self.adjacency_list) do
|
|
if node[key] == value then
|
|
return node
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- Runs function f for all nodes in the graph
|
|
-- graph = Graph()
|
|
-- graph:add_node(1)
|
|
-- graph:add_node('node_2')
|
|
-- graph:for_all_nodes(function(node) print(node) end) -> prints 1, 'node_2'
|
|
function Graph:for_all_nodes(f)
|
|
for _, node in ipairs(self.nodes) do
|
|
f(node)
|
|
end
|
|
end
|
|
|
|
|
|
-- Runs function f for all edges in the graph
|
|
-- graph = Graph()
|
|
-- graph:add_node(1)
|
|
-- graph:add_node('node_2')
|
|
-- graph:add_node(3)
|
|
-- graph:add_edge(1, 'node_2')
|
|
-- graph:add_edge('node_2', 3)
|
|
-- graph:for_all_edges(function(node1, node2) print(node1, node2) end) -> prints 1, 'node_2'; prints 'node_2', 3
|
|
function Graph:for_all_edges(f)
|
|
for _, edge in ipairs(self.edges) do
|
|
f(edge[1], edge[2])
|
|
end
|
|
end
|
|
|
|
|
|
-- Returns a table containing all neighbors of the given node.
|
|
-- graph = Graph()
|
|
-- graph:add_node(1)
|
|
-- graph:add_node('node_2')
|
|
-- graph:add_node(3)
|
|
-- graph:add_edge(1, 'node_2')
|
|
-- graph:add_edge('node_2', 3)
|
|
-- graph:get_node_neighbors('node_2') -> {1, 3}
|
|
function Graph:get_node_neighbors(node)
|
|
return self.adjacency_list[node]
|
|
end
|
|
|
|
|
|
-- graph = Graph()
|
|
-- graph:add_node(1)
|
|
-- graph:remove_node(1)
|
|
function Graph:remove_node(node)
|
|
for _node, list in pairs(self.adjacency_list) do
|
|
self:remove_edge(node, _node)
|
|
end
|
|
self.adjacency_list[node] = nil
|
|
self:_set_nodes()
|
|
end
|
|
|
|
|
|
local function contains_edge(table, edge)
|
|
for _, v in ipairs(table) do
|
|
if (v[1] == edge[1] and v[2] == edge[2]) or (v[1] == edge[2] and v[2] == edge[1]) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
|
|
-- graph = Graph()
|
|
-- graph:add_node(1)
|
|
-- graph:add_node('node_2')
|
|
-- graph:add_edge(1, 'node_2')
|
|
function Graph:add_edge(node1, node2)
|
|
if table.any(self.adjacency_list[node1], function(v) return v == node2 end) then return end
|
|
table.insert(self.adjacency_list[node1], node2)
|
|
table.insert(self.adjacency_list[node2], node1)
|
|
self:_set_edges()
|
|
end
|
|
|
|
|
|
-- graph = Graph()
|
|
-- graph:add_node(1)
|
|
-- graph:add_node('node_2')
|
|
-- graph:add_edge(1, 'node_2')
|
|
-- graph:remove_edge(1, 'node_2')
|
|
function Graph:remove_edge(node1, node2)
|
|
for i, node in ipairs(self.adjacency_list[node1]) do
|
|
if node == node2 then
|
|
table.remove(self.adjacency_list[node1], i)
|
|
break
|
|
end
|
|
end
|
|
for i, node in ipairs(self.adjacency_list[node2]) do
|
|
if node == node1 then
|
|
table.remove(self.adjacency_list[node2], i)
|
|
break
|
|
end
|
|
end
|
|
self:_set_edges()
|
|
end
|
|
|
|
|
|
-- graph = Graph()
|
|
-- graph:add_node(1)
|
|
-- graph:add_node('node_2')
|
|
-- graph:add_node('node_3')
|
|
-- graph:add_edge(1, 'node_2')
|
|
-- graph:add_edge('node_2', 'node_3')
|
|
-- path = graph:shortest_path_bfs(1, 'node_3')
|
|
-- print(path) -> {1, 'node_2', 'node_3'}
|
|
function Graph:shortest_path_bfs(node1, node2)
|
|
local path = {}
|
|
local visited = {}
|
|
local queue = {}
|
|
table.insert(queue, node1)
|
|
visited[node1] = true
|
|
|
|
while #queue > 0 do
|
|
local node = table.remove(queue, 1)
|
|
if node == node2 then
|
|
local linear_path = {}
|
|
local current_node = node2
|
|
table.insert(linear_path, 1, current_node)
|
|
while current_node ~= node1 do
|
|
current_node = path[current_node]
|
|
table.insert(linear_path, 1, current_node)
|
|
end
|
|
return linear_path
|
|
end
|
|
|
|
for _, neighbor in ipairs(self.adjacency_list[node]) do
|
|
if not visited[neighbor] then
|
|
path[neighbor] = node
|
|
visited[neighbor] = true
|
|
table.insert(queue, neighbor)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function Graph:get_distance_between_nodes(node1, node2)
|
|
return self.floyd_dists[node1][node2]
|
|
end
|
|
|
|
|
|
-- Comments follow pseudocode from http://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm.
|
|
-- graph = Graph()
|
|
-- graph:add_node(1)
|
|
-- graph:add_node('node_2')
|
|
-- graph:add_edge(1, 'node_2')
|
|
-- graph:floyd_warshall()
|
|
-- print(graph.floyd_dists[1]['node_2']) -> 1
|
|
function Graph:floyd_warshall()
|
|
self:_set_nodes()
|
|
self:_set_edges()
|
|
|
|
-- initialize multidimensional to be array
|
|
for _, node in ipairs(self.nodes) do
|
|
self.floyd_dists[node] = {}
|
|
end
|
|
|
|
-- let floyd_dist be a |V|x|V| array of minimun distance initialized to infinity
|
|
for _, node in ipairs(self.nodes) do
|
|
for _, _node in ipairs(self.nodes) do
|
|
self.floyd_dists[node][_node] = 10000 -- 10000 is big enough for an unweighted graph
|
|
self.floyd_dists[_node][node] = 10000
|
|
end
|
|
end
|
|
|
|
-- set dist[v][v] to 0
|
|
for _, node in ipairs(self.nodes) do
|
|
self.floyd_dists[node][node] = 0
|
|
end
|
|
|
|
-- set dist[u][v] to w(u, v) which is always 1 in the case of an unweighted graph
|
|
for _, edge in ipairs(self.edges) do
|
|
self.floyd_dists[edge[1]][edge[2]] = 1
|
|
self.floyd_dists[edge[2]][edge[1]] = 1
|
|
end
|
|
|
|
-- main triple loop
|
|
for _, nodek in ipairs(self.nodes) do
|
|
for _, nodei in ipairs(self.nodes) do
|
|
for _, nodej in ipairs(self.nodes) do
|
|
if self.floyd_dists[nodei][nodek] + self.floyd_dists[nodek][nodej] < self.floyd_dists[nodei][nodej] then
|
|
self.floyd_dists[nodei][nodej] = self.floyd_dists[nodei][nodek] + self.floyd_dists[nodek][nodej]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function Graph:_get_edge(node1, node2)
|
|
for _, node in ipairs(self.adjacency_list[node1]) do
|
|
if node == node2 then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
|
|
function Graph:_set_nodes()
|
|
self.nodes = {}
|
|
for node, _ in pairs(self.adjacency_list) do
|
|
table.insert(self.nodes, node)
|
|
end
|
|
end
|
|
|
|
|
|
function Graph:_set_edges()
|
|
self.edges = {}
|
|
for node, list in pairs(self.adjacency_list) do
|
|
for _, _node in ipairs(list) do
|
|
if not contains_edge(self.edges, {node, _node}) then
|
|
table.insert(self.edges, {node, _node})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function Graph:__tostring()
|
|
local str = "----\nAdjacency List: \n"
|
|
for node, list in pairs(self.adjacency_list) do
|
|
str = str .. node .. " -> "
|
|
for _, adj in ipairs(list) do
|
|
str = str .. adj .. ", "
|
|
end
|
|
str = string.sub(str, 0, -3)
|
|
str = str .. "\n"
|
|
end
|
|
str = str .. "\n"
|
|
str = str .. "Nodes: \n"
|
|
for _, node in ipairs(self.nodes) do
|
|
str = str .. node .. "\n"
|
|
end
|
|
str = str .. "\n"
|
|
str = str .. "Edges: \n"
|
|
for _, edge in ipairs(self.edges) do
|
|
str = str .. edge[1] .. ", " .. edge[2]
|
|
str = str .. "\n"
|
|
end
|
|
str = str .. "\n"
|
|
str = str .. "Floyd Warshall Distances: \n"
|
|
for node, _ in pairs(self.floyd_dists) do
|
|
for _node, _ in pairs(self.floyd_dists[node]) do
|
|
str = str .. "(" .. node .. ", " .. _node .. ") = " .. self.floyd_dists[node][_node]
|
|
str = str .. "\n"
|
|
end
|
|
end
|
|
str = str .. "----\n"
|
|
return str
|
|
end
|