@@ -115,3 +115,57 @@ def topsort(data: dict[T, set[T]]) -> Iterable[set[T]]:
115115 yield ready
116116 data = {item : (dep - ready ) for item , dep in data .items () if item not in ready }
117117 assert not data , f"A cyclic dependency exists amongst { data !r} "
118+
119+
120+ def topsort2 (data : dict [T , set [T ]]) -> Iterable [set [T ]]:
121+ """Topological sort using Kahn's algorithm.
122+
123+ This is functionally equivalent to topsort() but avoids rebuilding
124+ the full dict and set objects on each iteration. Instead it uses
125+ in-degree counters and a reverse adjacency list, so the total work
126+ is O(V + E) rather than O(depth * V).
127+
128+ Args:
129+ data: A map from vertices to all vertices that it has an edge
130+ connecting it to. NOTE: This data structure
131+ is modified in place -- for normalization purposes,
132+ self-dependencies are removed and entries representing
133+ orphans are added.
134+
135+ Returns:
136+ An iterator yielding sets of vertices that have an equivalent
137+ ordering.
138+ """
139+ for k , v in data .items ():
140+ v .discard (k ) # Ignore self dependencies.
141+ for item in set .union (* data .values ()) - set (data .keys ()):
142+ data [item ] = set ()
143+
144+ # Build reverse adjacency list and in-degree counts.
145+ in_degree : dict [T , int ] = {}
146+ rev : dict [T , list [T ]] = {}
147+ for item in data :
148+ in_degree [item ] = len (data [item ])
149+ rev [item ] = []
150+ for item , deps in data .items ():
151+ for dep in deps :
152+ rev [dep ].append (item )
153+
154+ ready = {item for item , deg in in_degree .items () if deg == 0 }
155+ remaining = len (in_degree ) - len (ready )
156+
157+ while ready :
158+ yield ready
159+ new_ready : set [T ] = set ()
160+ for item in ready :
161+ for dependent in rev [item ]:
162+ in_degree [dependent ] -= 1
163+ if in_degree [dependent ] == 0 :
164+ new_ready .add (dependent )
165+ remaining -= len (new_ready )
166+ ready = new_ready
167+
168+ assert remaining == 0 , (
169+ f"A cyclic dependency exists amongst "
170+ f"{ [k for k , deg in in_degree .items () if deg > 0 ]!r} "
171+ )
0 commit comments