Skip to content

Commit b52fcd2

Browse files
committed
WIP faster topsort
1 parent 248f09d commit b52fcd2

1 file changed

Lines changed: 54 additions & 0 deletions

File tree

mypy/graph_utils.py

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

Comments
 (0)