Skip to content

Commit

Permalink
Enhance debugging functions of Graph.lua
Browse files Browse the repository at this point in the history
  • Loading branch information
hansonchar committed Jan 14, 2025
1 parent 2d263c0 commit 01a2b43
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 39 deletions.
38 changes: 38 additions & 0 deletions learning-lua/algo/Dijkstra-test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,49 @@ local function negative_tests()
assert(string.match(errmsg, "Weight must not be negative$"))
end

-- CE 191 - CEE Systems Analysis by Processor Scott Moura, University of California, Berkeley
local function scott_moura_test()
print("scott_moura_test...")
local input<const> = {'A-B=2', 'A-C=4', 'A-D=4', 'B-F=6', 'C-F=5', 'C-E=7', 'C-D=1', 'D-E=5', 'D-H=11', 'E-G=3',
'E-H=4', 'F-H=5', 'F-G=2', 'F-E=1', 'G-H=2'}
local source<const> = 'A'

local G = load_input(input)
local d = Dijkstra:new(G, source)
local sssp = d:shortest_paths() -- shortest path to all vertices
sssp:dump()

assert(sssp:shortest_path_of('A') == 'A')
assert(sssp:min_cost(source) == 0)

assert(sssp:shortest_path_of('B') == 'A-B')
assert(sssp:min_cost('B') == 2)

assert(sssp:shortest_path_of('C') == 'A-C')
assert(sssp:min_cost('C') == 4)

assert(sssp:shortest_path_of('D') == 'A-D')
assert(sssp:min_cost('D') == 4)

assert(sssp:shortest_path_of('E') == 'A-D-E')
assert(sssp:min_cost('E') == 9)

assert(sssp:shortest_path_of('F') == 'A-B-F')
assert(sssp:min_cost('F') == 8)

assert(sssp:shortest_path_of('G') == 'A-B-F-G')
assert(sssp:min_cost('G') == 10)

assert(sssp:shortest_path_of('H') == 'A-B-F-G-H')
assert(sssp:min_cost('H') == 12)
end

basic_tests()
negative_tests()
tim_test()
geek_test()
redblobgames_test()
algodaily_test()
scott_moura_test()

os.exit()
8 changes: 4 additions & 4 deletions learning-lua/algo/Dijkstra.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ function Dijkstra:shortest_paths(Q)
if u == Q then
break -- if we are only interested in the shortest path to Q
end
local edges = G:edges(u)
if edges then
for v, weight in edges:outgoings() do
local vertex = G:vertex(u)
if vertex then
for v, weight in vertex:outgoings() do
assert(weight >= 0, "Weight must not be negative")
local v_info, v_cost_so_far = sssp.vertices[v], sssp.vertices[u].min_cost + weight
if v_cost_so_far < v_info.min_cost then
Expand Down Expand Up @@ -90,7 +90,7 @@ function Dijkstra:new(G, s)
assert(G, "Missing Graph")
assert(Graph.isGraph(G), "G must be a graph object")
assert(s, "Missing starting vertex")
assert(G:edges(s), "Starting vertex with at least one outgoing edge not found in graph")
assert(G:vertex(s), "Starting vertex with at least one outgoing edge not found in graph")
return self:class{
graph = G,
starting_vertex = s,
Expand Down
71 changes: 50 additions & 21 deletions learning-lua/algo/Graph-test.lua
Original file line number Diff line number Diff line change
@@ -1,35 +1,64 @@
local Graph = require "algo.Graph"

local G = Graph:new()
local function load_input(input)
local G = Graph:new()
for _, edge in ipairs(input) do
local from, to, cost = edge:match('(%w-)%-(%w+)=(%d+)')
G:add(from, to, cost)
end
return G
end

G:dump('x')
local function basic_tests()
print("basic_tests...")
local G = Graph:new()

G:add('s', 'v', 1)
G:add('s', 'w', 4)
G:add('v', 'w', 2)
print(G:outgoing_str('x'))

G:dump()
G:add('s', 'v', 1)
G:add('s', 'w', 4)
G:add('v', 'w', 2)

local edges = G:edges('s')
for v, weight in edges:outgoings() do
print('s', v, weight)
end
print(G:outgoing_str())

local s = G:vertex('s')
for v, weight in s:outgoings() do
print('s', v, weight)
end

print(G:outgoing_str('s'))

G:dump('s')
local x = {}
setmetatable(x, x)
assert(not Graph.isGraph(x)) -- self reference metatable

local x = {}
setmetatable(x, x)
assert(not Graph.isGraph(x)) -- self reference metatable
assert(Graph.isGraph(G)) -- the metatable of G is Graph
assert(not Graph.isGraph {}) -- no metatable

assert(Graph.isGraph(G)) -- the metatable of G is Graph
assert(not Graph.isGraph{}) -- no metatable
local child = G:new() -- the metatable of the metatable of child is Graph
assert(Graph.isGraph(child))

local child = G:new() -- the metatable of the metatable of child is Graph
assert(Graph.isGraph(child))
assert(not Graph.isGraph(1))
assert(not Graph.isGraph(""))

assert(not Graph.isGraph(1))
assert(not Graph.isGraph(""))
assert(not Graph.isGraph())
end

local function test_build_ingress()
print("test_build_ingress ... ")
local input<const> = {'A-B=2', 'A-C=4', 'A-D=4', 'B-F=6', 'C-F=5', 'C-E=7', 'C-D=1', 'D-E=5', 'D-H=11', 'E-G=3',
'E-H=4', 'F-H=5', 'F-G=2', 'F-E=1', 'G-H=2'}
local source<const> = 'A'
local G = load_input(input)
G:build_ingress()
local incoming, count_i = G:incoming_str()
-- print(incoming)
local outgoing, count_e = G:outgoing_str()
-- print(outgoing)
assert(count_e == count_i)
end

assert(not Graph.isGraph())
basic_tests()
test_build_ingress()

os.exit()
68 changes: 54 additions & 14 deletions learning-lua/algo/Graph.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ local mt_vertex = {}
function mt_vertex:outgoings()
return pairs(self.egress and self.egress or E)
end
function mt_vertex:incomings()
return pairs(self.ingress and self.ingress or E)
end
mt_vertex.__index = mt_vertex

--- Adds a vertex u and optionally a weighted directional edge from u to v.
Expand All @@ -31,45 +34,82 @@ function Graph:add(u, v, weight)
end
end

function Graph:build_ingress()
for u, u_node in pairs(self) do
for v, weight in pairs(u_node.egress or E) do
local v_node = self[v]
v_node.ingress = v_node.ingress or {}
v_node.ingress[u] = weight
end
end
end

function Graph:vertices()
return pairs(self)
end

---@param v (string)
function Graph:edges(v)
function Graph:vertex(v)
return self[v]
end

---@param u (string)
---@param vertex_u (table)
---@param tostring_f (function) apply this function to u for display purposes
---@return (number) the number of edges printed
local function print_outgoings(u, vertex_u, tostring_f)
---@return (table) an array of all the outgoing edges of u for debugging purposes
local function outgoing_str_of(u, vertex_u, tostring_f)
local a = {}
local u_str = tostring_f and tostring_f(u) or u
local count = 0
for v, weight in vertex_u:outgoings() do
local v_str = tostring_f and tostring_f(v) or v
print(string.format("%s->%s:%d", u_str, v_str, weight))
count = count + 1
a[#a + 1] = string.format("%s->%s:%d", u_str, v_str, weight)
end
return count
return a
end

---@param u (string) vertex
---@param u (string) vertex u or nil for all vertices
---@param tostring_f (function) apply this function to u for display purposes
---@return (number) the number of edges dumped
function Graph:dump(u, tostring_f)
local count = 0
---@return (string), (number) a string of all the outgoing edges (of either vertex u if specified or of all vertices otherwise) for debugging purposes, and a count of the edges.
function Graph:outgoing_str(u, tostring_f)
local a = {}
if u then
if self[u] then
count = count + print_outgoings(u, self[u], tostring_f)
local from = outgoing_str_of(u, self[u], tostring_f)
table.move(from, 1, #from, #a + 1, a)
end
else
for u, vertex_u in self:vertices() do
count = count + print_outgoings(u, vertex_u, tostring_f)
local from = outgoing_str_of(u, vertex_u, tostring_f)
table.move(from, 1, #from, #a + 1, a)
end
end
return table.concat(a, ","), #a
end

local function incoming_str_of(v, vertex_v, tostring_f)
local a = {}
local v_str = tostring_f and tostring_f(v) or v
for u, weight in vertex_v:incomings() do
local u_str = tostring_f and tostring_f(u) or u
a[#a + 1] = string.format("%s->%s:%d", u_str, v_str, weight)
end
return a
end

function Graph:incoming_str(v, tostring_f)
local a = {}
if v then
if self[v] then
local from = incoming_str_of(v, self[v], tostring_f)
table.move(from, 1, #from, #a + 1, a)
end
else
for v, vertex_v in self:vertices() do
local from = incoming_str_of(v, vertex_v, tostring_f)
table.move(from, 1, #from, #a + 1, a)
end
end
return count
return table.concat(a, ","), #a
end

function Graph.isGraph(o)
Expand Down

0 comments on commit 01a2b43

Please sign in to comment.