Skip to content

Commit 3e005b6

Browse files
fix: graph.omc bare-return/inner-fn/cycle-check; add 13 graph lib tests
- Convert bare `return` to `return null;` (parser bug workaround) - Move _cycle_check and _topo_visit to top-level with explicit params (inner named fns don't capture outer scope in OMC) - Rename shadowed loop var `n` → `n2` in dijkstra - Add dict_has guard in dijkstra neighbor relaxation - Add 13 tests: BFS, DFS, Dijkstra, shortest_path, toposort, cycle detection, connected components, PageRank Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 450d068 commit 3e005b6

2 files changed

Lines changed: 212 additions & 63 deletions

File tree

examples/lib/graph.omc

Lines changed: 66 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# graph.omc — graph algorithms: BFS, DFS, Dijkstra, toposort, PageRank, SCC
1+
# graph.omc — graph algorithms: BFS, DFS, Dijkstra, toposort, PageRank, connected components
22

33
# ── Graph construction ──────────────────────────────────────────────────────
44

@@ -13,29 +13,29 @@ fn graph_new_undirected() {
1313

1414
# graph_add_node(g, id, data?)
1515
fn graph_add_node(g, id, data) {
16-
h key = to_str(id)
16+
h key = to_string(id)
1717
g["nodes"][key] = data
18-
if not dict_has(g["edges"], key) {
18+
if !dict_has(g["edges"], key) {
1919
g["edges"][key] = []
2020
}
2121
}
2222

2323
# graph_add_edge(g, from, to, weight?)
2424
fn graph_add_edge(g, from_node, to_node, weight) {
25-
h f = to_str(from_node)
26-
h t = to_str(to_node)
25+
h f = to_string(from_node)
26+
h t = to_string(to_node)
2727
h w = weight
2828
if w == null { w = 1 }
29-
if not dict_has(g["edges"], f) { g["edges"][f] = [] }
30-
if not dict_has(g["edges"], t) { g["edges"][t] = [] }
29+
if !dict_has(g["edges"], f) { g["edges"][f] = [] }
30+
if !dict_has(g["edges"], t) { g["edges"][t] = [] }
3131
arr_push(g["edges"][f], {to: t, weight: w})
32-
if not g["directed"] {
32+
if !g["directed"] {
3333
arr_push(g["edges"][t], {to: f, weight: w})
3434
}
3535
}
3636

3737
fn graph_neighbors(g, node) {
38-
h key = to_str(node)
38+
h key = to_string(node)
3939
if dict_has(g["edges"], key) { return g["edges"][key] }
4040
return []
4141
}
@@ -45,9 +45,9 @@ fn graph_neighbors(g, node) {
4545
# bfs(g, start) → [node_ids in visit order]
4646
fn bfs(g, start) {
4747
h visited = {}
48-
h queue = [to_str(start)]
48+
h queue = [to_string(start)]
4949
h order = []
50-
visited[to_str(start)] = true
50+
visited[to_string(start)] = true
5151
while arr_len(queue) > 0 {
5252
h current = queue[0]
5353
queue = arr_slice(queue, 1, arr_len(queue))
@@ -56,7 +56,7 @@ fn bfs(g, start) {
5656
h i = 0
5757
while i < arr_len(neighbors) {
5858
h nb = neighbors[i]["to"]
59-
if not dict_has(visited, nb) {
59+
if !dict_has(visited, nb) {
6060
visited[nb] = true
6161
arr_push(queue, nb)
6262
}
@@ -66,13 +66,13 @@ fn bfs(g, start) {
6666
return order
6767
}
6868

69-
# bfs_path(g, start, end) → [path] or null
69+
# bfs_path(g, start, goal) → [path] or null
7070
fn bfs_path(g, start, goal) {
7171
h visited = {}
7272
h parent = {}
73-
h queue = [to_str(start)]
74-
h s = to_str(start)
75-
h e = to_str(goal)
73+
h queue = [to_string(start)]
74+
h s = to_string(start)
75+
h e = to_string(goal)
7676
visited[s] = true
7777
parent[s] = null
7878
while arr_len(queue) > 0 {
@@ -91,7 +91,7 @@ fn bfs_path(g, start, goal) {
9191
h i = 0
9292
while i < arr_len(neighbors) {
9393
h nb = neighbors[i]["to"]
94-
if not dict_has(visited, nb) {
94+
if !dict_has(visited, nb) {
9595
visited[nb] = true
9696
parent[nb] = current
9797
arr_push(queue, nb)
@@ -111,7 +111,7 @@ fn _dfs_visit(g, node, visited, order) {
111111
h i = 0
112112
while i < arr_len(neighbors) {
113113
h nb = neighbors[i]["to"]
114-
if not dict_has(visited, nb) {
114+
if !dict_has(visited, nb) {
115115
_dfs_visit(g, nb, visited, order)
116116
}
117117
i = i + 1
@@ -122,39 +122,39 @@ fn _dfs_visit(g, node, visited, order) {
122122
fn dfs(g, start) {
123123
h visited = {}
124124
h order = []
125-
_dfs_visit(g, to_str(start), visited, order)
125+
_dfs_visit(g, to_string(start), visited, order)
126126
return order
127127
}
128128

129129
# ── Topological sort ─────────────────────────────────────────────────────────
130130

131-
fn _topo_visit(g, node, visited, stack, cyclic) {
132-
if dict_has(cyclic, node) { return }
133-
if dict_has(visited, node) { return }
134-
cyclic[node] = true
131+
fn _topo_visit(g, node, visited, stack, in_progress) {
132+
if dict_has(in_progress, node) { return null; }
133+
if dict_has(visited, node) { return null; }
134+
in_progress[node] = true
135135
h neighbors = graph_neighbors(g, node)
136136
h i = 0
137137
while i < arr_len(neighbors) {
138138
h nb = neighbors[i]["to"]
139-
_topo_visit(g, nb, visited, stack, cyclic)
139+
_topo_visit(g, nb, visited, stack, in_progress)
140140
i = i + 1
141141
}
142-
dict_delete(cyclic, node)
142+
dict_delete(in_progress, node)
143143
visited[node] = true
144144
arr_push(stack, node)
145145
}
146146

147-
# toposort(g) → [node_ids] in topological order, or null if cycle
147+
# toposort(g) → [node_ids] in topological order
148148
fn toposort(g) {
149149
h visited = {}
150150
h stack = []
151-
h cyclic = {}
151+
h in_progress = {}
152152
h nodes = dict_keys(g["nodes"])
153153
h i = 0
154154
while i < arr_len(nodes) {
155155
h n = nodes[i]
156-
if not dict_has(visited, n) {
157-
_topo_visit(g, n, visited, stack, cyclic)
156+
if !dict_has(visited, n) {
157+
_topo_visit(g, n, visited, stack, in_progress)
158158
}
159159
i = i + 1
160160
}
@@ -165,7 +165,7 @@ fn toposort(g) {
165165

166166
# dijkstra(g, start) → {dist: {node: dist}, prev: {node: prev}}
167167
fn dijkstra(g, start) {
168-
h s = to_str(start)
168+
h s = to_string(start)
169169
h dist = {}
170170
h prev = {}
171171
h unvisited = []
@@ -180,7 +180,6 @@ fn dijkstra(g, start) {
180180
}
181181
dist[s] = 0
182182
while arr_len(unvisited) > 0 {
183-
# find min dist unvisited node
184183
h u = null
185184
h min_d = 999999999
186185
h j = 0
@@ -193,7 +192,6 @@ fn dijkstra(g, start) {
193192
j = j + 1
194193
}
195194
if u == null { break }
196-
# remove u from unvisited
197195
h new_unvisited = []
198196
h k = 0
199197
while k < arr_len(unvisited) {
@@ -202,27 +200,29 @@ fn dijkstra(g, start) {
202200
}
203201
unvisited = new_unvisited
204202
h neighbors = graph_neighbors(g, u)
205-
h n = 0
206-
while n < arr_len(neighbors) {
207-
h edge = neighbors[n]
203+
h n2 = 0
204+
while n2 < arr_len(neighbors) {
205+
h edge = neighbors[n2]
208206
h v = edge["to"]
209-
h alt = dist[u] + edge["weight"]
210-
if alt < dist[v] {
211-
dist[v] = alt
212-
prev[v] = u
207+
if dict_has(dist, v) {
208+
h alt = dist[u] + edge["weight"]
209+
if alt < dist[v] {
210+
dist[v] = alt
211+
prev[v] = u
212+
}
213213
}
214-
n = n + 1
214+
n2 = n2 + 1
215215
}
216216
}
217217
return {dist: dist, prev: prev}
218218
}
219219

220-
# shortest_path(g, start, end) → [path] or null
220+
# shortest_path(g, start, goal) → [path] or null
221221
fn shortest_path(g, start, goal) {
222222
h result = dijkstra(g, start)
223223
h prev = result["prev"]
224-
h e = to_str(goal)
225-
if prev[e] == null and e != to_str(start) { return null }
224+
h e = to_string(goal)
225+
if prev[e] == null and e != to_string(start) { return null; }
226226
h path = []
227227
h node = e
228228
while node != null {
@@ -290,7 +290,7 @@ fn connected_components(g) {
290290
h i = 0
291291
while i < arr_len(nodes) {
292292
h n = nodes[i]
293-
if not dict_has(visited, n) {
293+
if !dict_has(visited, n) {
294294
h order = []
295295
_dfs_visit(g, n, visited, order)
296296
arr_push(components, order)
@@ -300,33 +300,36 @@ fn connected_components(g) {
300300
return components
301301
}
302302

303+
# ── Cycle detection ──────────────────────────────────────────────────────────
304+
305+
fn _cycle_check(g, node, visited, rec_stack) {
306+
visited[node] = true
307+
rec_stack[node] = true
308+
h neighbors = graph_neighbors(g, node)
309+
h i = 0
310+
while i < arr_len(neighbors) {
311+
h nb = neighbors[i]["to"]
312+
if !dict_has(visited, nb) {
313+
if _cycle_check(g, nb, visited, rec_stack) { return true }
314+
} elif dict_has(rec_stack, nb) {
315+
return true
316+
}
317+
i = i + 1
318+
}
319+
dict_delete(rec_stack, node)
320+
return false
321+
}
322+
303323
# has_cycle(g) → bool
304324
fn has_cycle(g) {
305325
h visited = {}
306326
h rec_stack = {}
307-
fn _cycle_check(node) {
308-
visited[node] = true
309-
rec_stack[node] = true
310-
h neighbors = graph_neighbors(g, node)
311-
h i = 0
312-
while i < arr_len(neighbors) {
313-
h nb = neighbors[i]["to"]
314-
if not dict_has(visited, nb) {
315-
if _cycle_check(nb) { return true }
316-
} else if dict_has(rec_stack, nb) {
317-
return true
318-
}
319-
i = i + 1
320-
}
321-
dict_delete(rec_stack, node)
322-
return false
323-
}
324327
h nodes = dict_keys(g["nodes"])
325328
h i = 0
326329
while i < arr_len(nodes) {
327330
h n = nodes[i]
328-
if not dict_has(visited, n) {
329-
if _cycle_check(n) { return true }
331+
if !dict_has(visited, n) {
332+
if _cycle_check(g, n, visited, rec_stack) { return true }
330333
}
331334
i = i + 1
332335
}

0 commit comments

Comments
 (0)