Skip to content

Commit 6f1249a

Browse files
committed
Switch to a class instead of iterator function
1 parent b52fcd2 commit 6f1249a

1 file changed

Lines changed: 44 additions & 33 deletions

File tree

mypy/graph_utils.py

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)