@@ -117,55 +117,66 @@ def topsort(data: dict[T, set[T]]) -> Iterable[set[T]]:
117117 assert not data , f"A cyclic dependency exists amongst { data !r} "
118118
119119
120- def topsort2 (data : dict [ T , set [T ]]) -> Iterable [ set [ T ]] :
120+ class topsort2 (Iterator [ set [T ]]):
121121 """Topological sort using Kahn's algorithm.
122122
123123 This is functionally equivalent to topsort() but avoids rebuilding
124124 the full dict and set objects on each iteration. Instead it uses
125125 in-degree counters and a reverse adjacency list, so the total work
126126 is O(V + E) rather than O(depth * V).
127127
128+ Implemented as a class rather than a generator for better mypyc
129+ compilation.
130+
128131 Args:
129132 data: A map from vertices to all vertices that it has an edge
130133 connecting it to. NOTE: This data structure
131134 is modified in place -- for normalization purposes,
132135 self-dependencies are removed and entries representing
133136 orphans are added.
134-
135- Returns:
136- An iterator yielding sets of vertices that have an equivalent
137- ordering.
138137 """
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 )
156138
157- while ready :
158- yield ready
139+ def __init__ (self , data : dict [T , set [T ]]) -> None :
140+ for k , v in data .items ():
141+ v .discard (k ) # Ignore self dependencies.
142+ for item in set .union (* data .values ()) - set (data .keys ()):
143+ data [item ] = set ()
144+
145+ # Build reverse adjacency list and in-degree counts.
146+ in_degree : dict [T , int ] = {}
147+ rev : dict [T , list [T ]] = {}
148+ for item in data :
149+ in_degree [item ] = len (data [item ])
150+ rev [item ] = []
151+ for item , deps in data .items ():
152+ for dep in deps :
153+ rev [dep ].append (item )
154+
155+ self .in_degree = in_degree
156+ self .rev = rev
157+ self .ready = {item for item , deg in in_degree .items () if deg == 0 }
158+ self .remaining = len (in_degree ) - len (self .ready )
159+
160+ def __iter__ (self ) -> Iterator [set [T ]]:
161+ return self
162+
163+ def __next__ (self ) -> set [T ]:
164+ ready = self .ready
165+ if not ready :
166+ assert self .remaining == 0 , (
167+ f"A cyclic dependency exists amongst "
168+ f"{ [k for k , deg in self .in_degree .items () if deg > 0 ]!r} "
169+ )
170+ raise StopIteration
171+ in_degree = self .in_degree
172+ rev = self .rev
159173 new_ready : set [T ] = set ()
160174 for item in ready :
161175 for dependent in rev [item ]:
162- in_degree [dependent ] -= 1
163- if in_degree [dependent ] == 0 :
176+ new_deg = in_degree [dependent ] - 1
177+ in_degree [dependent ] = new_deg
178+ if new_deg == 0 :
164179 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- )
180+ self .remaining -= len (new_ready )
181+ self .ready = new_ready
182+ return ready
0 commit comments