fromscratch-pygraphdb 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pygraphdb/traversal.py ADDED
@@ -0,0 +1,105 @@
1
+ """Graph traversal algorithms -- BFS and DFS.
2
+
3
+ Imagine you're exploring a maze. There are two main strategies:
4
+
5
+ **BFS (Breadth-First Search)** -- Like ripples in a pond. You explore
6
+ all rooms one step away first, then all rooms two steps away, and so on.
7
+ This guarantees you find the shortest path (fewest steps) first.
8
+
9
+ **DFS (Depth-First Search)** -- Like following one tunnel as deep as
10
+ you can go before backtracking. You pick a direction and keep going
11
+ until you hit a dead end, then you go back and try another path.
12
+
13
+ Both algorithms visit every reachable node exactly once, but in
14
+ different orders. BFS uses a queue (first-in, first-out), while
15
+ DFS uses a stack (last-in, first-out).
16
+
17
+ Example:
18
+ >>> from pygraphdb.graph import Graph
19
+ >>> from pygraphdb.traversal import bfs, dfs
20
+ >>> g = Graph()
21
+ >>> g.add_node("a")
22
+ >>> g.add_node("b")
23
+ >>> g.add_node("c")
24
+ >>> g.add_edge("e1", "a", "b")
25
+ >>> g.add_edge("e2", "b", "c")
26
+ >>> [n.node_id for n in bfs(g, "a")]
27
+ ['a', 'b', 'c']
28
+
29
+ """
30
+
31
+ from collections import deque
32
+
33
+ from pygraphdb.graph import Graph
34
+ from pygraphdb.node import Node
35
+
36
+
37
+ def bfs(graph: Graph, start_id: str) -> list[Node]:
38
+ """Perform a breadth-first search starting from a given node.
39
+
40
+ Like ripples spreading outward from a stone dropped in water:
41
+ visit all neighbors first, then neighbors-of-neighbors, etc.
42
+
43
+ Args:
44
+ graph: The graph to traverse.
45
+ start_id: The ID of the starting node.
46
+
47
+ Returns:
48
+ A list of nodes in BFS visit order.
49
+
50
+ Raises:
51
+ NodeNotFoundError: If the start node does not exist.
52
+
53
+ """
54
+ graph.get_node(start_id) # Validate existence
55
+ visited: set[str] = set()
56
+ result: list[Node] = []
57
+ queue: deque[str] = deque([start_id])
58
+ visited.add(start_id)
59
+
60
+ while queue:
61
+ current_id = queue.popleft()
62
+ node = graph.get_node(current_id)
63
+ result.append(node)
64
+ for edge in graph.get_outgoing_edges(current_id):
65
+ if edge.target_id not in visited:
66
+ visited.add(edge.target_id)
67
+ queue.append(edge.target_id)
68
+
69
+ return result
70
+
71
+
72
+ def dfs(graph: Graph, start_id: str) -> list[Node]:
73
+ """Perform a depth-first search starting from a given node.
74
+
75
+ Like exploring a cave -- go as deep as you can down one tunnel,
76
+ then backtrack and try the next tunnel.
77
+
78
+ Args:
79
+ graph: The graph to traverse.
80
+ start_id: The ID of the starting node.
81
+
82
+ Returns:
83
+ A list of nodes in DFS visit order.
84
+
85
+ Raises:
86
+ NodeNotFoundError: If the start node does not exist.
87
+
88
+ """
89
+ graph.get_node(start_id) # Validate existence
90
+ visited: set[str] = set()
91
+ result: list[Node] = []
92
+ stack: list[str] = [start_id]
93
+
94
+ while stack:
95
+ current_id = stack.pop()
96
+ if current_id in visited:
97
+ continue
98
+ visited.add(current_id)
99
+ node = graph.get_node(current_id)
100
+ result.append(node)
101
+ # Add neighbors in reverse order so we visit them in a predictable order
102
+ outgoing = graph.get_outgoing_edges(current_id)
103
+ stack.extend(edge.target_id for edge in reversed(outgoing) if edge.target_id not in visited)
104
+
105
+ return result