Skip to content

Commit 03df3d8

Browse files
committed
Refactor walk_until through fold_walk
1 parent cfedf6b commit 03df3d8

File tree

1 file changed

+35
-151
lines changed

1 file changed

+35
-151
lines changed

src/yog/traversal.gleam

Lines changed: 35 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,14 @@ pub fn walk(
5353
in graph: Graph(n, e),
5454
using order: Order,
5555
) -> List(NodeId) {
56-
case order {
57-
BreadthFirst ->
58-
do_walk_bfs(graph, queue.new() |> queue.push(start_id), set.new(), [])
59-
DepthFirst -> do_walk_dfs(graph, [start_id], set.new(), [])
60-
}
56+
fold_walk(
57+
over: graph,
58+
from: start_id,
59+
using: order,
60+
initial: [],
61+
with: fn(acc, node_id, _meta) { #(Continue, [node_id, ..acc]) },
62+
)
63+
|> list.reverse()
6164
}
6265

6366
/// Walks the graph but stops early when a condition is met.
@@ -82,18 +85,20 @@ pub fn walk_until(
8285
using order: Order,
8386
until should_stop: fn(NodeId) -> Bool,
8487
) -> List(NodeId) {
85-
case order {
86-
BreadthFirst ->
87-
do_walk_until_bfs(
88-
graph,
89-
queue.new() |> queue.push(start_id),
90-
set.new(),
91-
[],
92-
should_stop,
93-
)
94-
DepthFirst ->
95-
do_walk_until_dfs(graph, [start_id], set.new(), [], should_stop)
96-
}
88+
fold_walk(
89+
over: graph,
90+
from: start_id,
91+
using: order,
92+
initial: [],
93+
with: fn(acc, node_id, _meta) {
94+
let new_acc = [node_id, ..acc]
95+
case should_stop(node_id) {
96+
True -> #(Halt, new_acc)
97+
False -> #(Continue, new_acc)
98+
}
99+
},
100+
)
101+
|> list.reverse()
97102
}
98103

99104
/// Folds over nodes during graph traversal, accumulating state with metadata.
@@ -212,132 +217,6 @@ pub fn fold_walk(
212217
}
213218
}
214219

215-
// BFS with efficient O(1) amortized queue operations
216-
fn do_walk_bfs(
217-
graph: Graph(n, e),
218-
q: queue.Queue(NodeId),
219-
visited: Set(NodeId),
220-
acc: List(NodeId),
221-
) -> List(NodeId) {
222-
case queue.pop(q) {
223-
Error(Nil) -> list.reverse(acc)
224-
Ok(#(head, rest)) -> {
225-
case set.contains(visited, head) {
226-
True -> do_walk_bfs(graph, rest, visited, acc)
227-
False -> {
228-
let next_nodes = model.successor_ids(graph, head)
229-
let next_queue = queue.push_list(rest, next_nodes)
230-
231-
do_walk_bfs(graph, next_queue, set.insert(visited, head), [
232-
head,
233-
..acc
234-
])
235-
}
236-
}
237-
}
238-
}
239-
}
240-
241-
// DFS with list-based stack (prepend is O(1))
242-
fn do_walk_dfs(
243-
graph: Graph(n, e),
244-
stack: List(NodeId),
245-
visited: Set(NodeId),
246-
acc: List(NodeId),
247-
) -> List(NodeId) {
248-
case stack {
249-
[] -> list.reverse(acc)
250-
[head, ..tail] -> {
251-
case set.contains(visited, head) {
252-
True -> do_walk_dfs(graph, tail, visited, acc)
253-
False -> {
254-
let next_nodes = model.successor_ids(graph, head)
255-
let next_stack = list.append(next_nodes, tail)
256-
257-
do_walk_dfs(graph, next_stack, set.insert(visited, head), [
258-
head,
259-
..acc
260-
])
261-
}
262-
}
263-
}
264-
}
265-
}
266-
267-
// BFS with early termination
268-
fn do_walk_until_bfs(
269-
graph: Graph(n, e),
270-
q: queue.Queue(NodeId),
271-
visited: Set(NodeId),
272-
acc: List(NodeId),
273-
should_stop: fn(NodeId) -> Bool,
274-
) -> List(NodeId) {
275-
case queue.pop(q) {
276-
Error(Nil) -> list.reverse(acc)
277-
Ok(#(head, rest)) -> {
278-
case set.contains(visited, head) {
279-
True -> do_walk_until_bfs(graph, rest, visited, acc, should_stop)
280-
False -> {
281-
let current_acc = [head, ..acc]
282-
283-
case should_stop(head) {
284-
True -> list.reverse(current_acc)
285-
False -> {
286-
let next_nodes = model.successor_ids(graph, head)
287-
let next_queue = queue.push_list(rest, next_nodes)
288-
289-
do_walk_until_bfs(
290-
graph,
291-
next_queue,
292-
set.insert(visited, head),
293-
current_acc,
294-
should_stop,
295-
)
296-
}
297-
}
298-
}
299-
}
300-
}
301-
}
302-
}
303-
304-
// DFS with early termination
305-
fn do_walk_until_dfs(
306-
graph: Graph(n, e),
307-
stack: List(NodeId),
308-
visited: Set(NodeId),
309-
acc: List(NodeId),
310-
should_stop: fn(NodeId) -> Bool,
311-
) -> List(NodeId) {
312-
case stack {
313-
[] -> list.reverse(acc)
314-
[head, ..tail] -> {
315-
case set.contains(visited, head) {
316-
True -> do_walk_until_dfs(graph, tail, visited, acc, should_stop)
317-
False -> {
318-
let current_acc = [head, ..acc]
319-
320-
case should_stop(head) {
321-
True -> list.reverse(current_acc)
322-
False -> {
323-
let next_nodes = model.successor_ids(graph, head)
324-
let next_stack = list.append(next_nodes, tail)
325-
326-
do_walk_until_dfs(
327-
graph,
328-
next_stack,
329-
set.insert(visited, head),
330-
current_acc,
331-
should_stop,
332-
)
333-
}
334-
}
335-
}
336-
}
337-
}
338-
}
339-
}
340-
341220
// BFS with fold and metadata
342221
fn do_fold_walk_bfs(
343222
graph: Graph(n, e),
@@ -405,15 +284,20 @@ fn do_fold_walk_dfs(
405284
Continue -> {
406285
// Add successors to stack with updated metadata
407286
let next_nodes = model.successor_ids(graph, node_id)
287+
// Reverse to maintain correct DFS order (since we prepend during fold)
408288
let next_stack =
409-
list.fold(next_nodes, tail, fn(current_stack, next_id) {
410-
let next_meta =
411-
WalkMetadata(
412-
depth: metadata.depth + 1,
413-
parent: Some(node_id),
414-
)
415-
[#(next_id, next_meta), ..current_stack]
416-
})
289+
list.fold(
290+
list.reverse(next_nodes),
291+
tail,
292+
fn(current_stack, next_id) {
293+
let next_meta =
294+
WalkMetadata(
295+
depth: metadata.depth + 1,
296+
parent: Some(node_id),
297+
)
298+
[#(next_id, next_meta), ..current_stack]
299+
},
300+
)
417301

418302
do_fold_walk_dfs(graph, next_stack, new_visited, new_acc, folder)
419303
}

0 commit comments

Comments
 (0)