Edit File by line
/home/barbar84/public_h.../wp-conte.../plugins/sujqvwi/AnonR/anonr.TX.../opt/imh-pyth.../lib/python3....
File: graphlib.py
__all__ = ["TopologicalSorter", "CycleError"]
[0] Fix | Delete
[1] Fix | Delete
_NODE_OUT = -1
[2] Fix | Delete
_NODE_DONE = -2
[3] Fix | Delete
[4] Fix | Delete
[5] Fix | Delete
class _NodeInfo:
[6] Fix | Delete
__slots__ = "node", "npredecessors", "successors"
[7] Fix | Delete
[8] Fix | Delete
def __init__(self, node):
[9] Fix | Delete
# The node this class is augmenting.
[10] Fix | Delete
self.node = node
[11] Fix | Delete
[12] Fix | Delete
# Number of predecessors, generally >= 0. When this value falls to 0,
[13] Fix | Delete
# and is returned by get_ready(), this is set to _NODE_OUT and when the
[14] Fix | Delete
# node is marked done by a call to done(), set to _NODE_DONE.
[15] Fix | Delete
self.npredecessors = 0
[16] Fix | Delete
[17] Fix | Delete
# List of successor nodes. The list can contain duplicated elements as
[18] Fix | Delete
# long as they're all reflected in the successor's npredecessors attribute).
[19] Fix | Delete
self.successors = []
[20] Fix | Delete
[21] Fix | Delete
[22] Fix | Delete
class CycleError(ValueError):
[23] Fix | Delete
"""Subclass of ValueError raised by TopologicalSorter.prepare if cycles
[24] Fix | Delete
exist in the working graph.
[25] Fix | Delete
[26] Fix | Delete
If multiple cycles exist, only one undefined choice among them will be reported
[27] Fix | Delete
and included in the exception. The detected cycle can be accessed via the second
[28] Fix | Delete
element in the *args* attribute of the exception instance and consists in a list
[29] Fix | Delete
of nodes, such that each node is, in the graph, an immediate predecessor of the
[30] Fix | Delete
next node in the list. In the reported list, the first and the last node will be
[31] Fix | Delete
the same, to make it clear that it is cyclic.
[32] Fix | Delete
"""
[33] Fix | Delete
[34] Fix | Delete
pass
[35] Fix | Delete
[36] Fix | Delete
[37] Fix | Delete
class TopologicalSorter:
[38] Fix | Delete
"""Provides functionality to topologically sort a graph of hashable nodes"""
[39] Fix | Delete
[40] Fix | Delete
def __init__(self, graph=None):
[41] Fix | Delete
self._node2info = {}
[42] Fix | Delete
self._ready_nodes = None
[43] Fix | Delete
self._npassedout = 0
[44] Fix | Delete
self._nfinished = 0
[45] Fix | Delete
[46] Fix | Delete
if graph is not None:
[47] Fix | Delete
for node, predecessors in graph.items():
[48] Fix | Delete
self.add(node, *predecessors)
[49] Fix | Delete
[50] Fix | Delete
def _get_nodeinfo(self, node):
[51] Fix | Delete
if (result := self._node2info.get(node)) is None:
[52] Fix | Delete
self._node2info[node] = result = _NodeInfo(node)
[53] Fix | Delete
return result
[54] Fix | Delete
[55] Fix | Delete
def add(self, node, *predecessors):
[56] Fix | Delete
"""Add a new node and its predecessors to the graph.
[57] Fix | Delete
[58] Fix | Delete
Both the *node* and all elements in *predecessors* must be hashable.
[59] Fix | Delete
[60] Fix | Delete
If called multiple times with the same node argument, the set of dependencies
[61] Fix | Delete
will be the union of all dependencies passed in.
[62] Fix | Delete
[63] Fix | Delete
It is possible to add a node with no dependencies (*predecessors* is not provided)
[64] Fix | Delete
as well as provide a dependency twice. If a node that has not been provided before
[65] Fix | Delete
is included among *predecessors* it will be automatically added to the graph with
[66] Fix | Delete
no predecessors of its own.
[67] Fix | Delete
[68] Fix | Delete
Raises ValueError if called after "prepare".
[69] Fix | Delete
"""
[70] Fix | Delete
if self._ready_nodes is not None:
[71] Fix | Delete
raise ValueError("Nodes cannot be added after a call to prepare()")
[72] Fix | Delete
[73] Fix | Delete
# Create the node -> predecessor edges
[74] Fix | Delete
nodeinfo = self._get_nodeinfo(node)
[75] Fix | Delete
nodeinfo.npredecessors += len(predecessors)
[76] Fix | Delete
[77] Fix | Delete
# Create the predecessor -> node edges
[78] Fix | Delete
for pred in predecessors:
[79] Fix | Delete
pred_info = self._get_nodeinfo(pred)
[80] Fix | Delete
pred_info.successors.append(node)
[81] Fix | Delete
[82] Fix | Delete
def prepare(self):
[83] Fix | Delete
"""Mark the graph as finished and check for cycles in the graph.
[84] Fix | Delete
[85] Fix | Delete
If any cycle is detected, "CycleError" will be raised, but "get_ready" can
[86] Fix | Delete
still be used to obtain as many nodes as possible until cycles block more
[87] Fix | Delete
progress. After a call to this function, the graph cannot be modified and
[88] Fix | Delete
therefore no more nodes can be added using "add".
[89] Fix | Delete
"""
[90] Fix | Delete
if self._ready_nodes is not None:
[91] Fix | Delete
raise ValueError("cannot prepare() more than once")
[92] Fix | Delete
[93] Fix | Delete
self._ready_nodes = [
[94] Fix | Delete
i.node for i in self._node2info.values() if i.npredecessors == 0
[95] Fix | Delete
]
[96] Fix | Delete
# ready_nodes is set before we look for cycles on purpose:
[97] Fix | Delete
# if the user wants to catch the CycleError, that's fine,
[98] Fix | Delete
# they can continue using the instance to grab as many
[99] Fix | Delete
# nodes as possible before cycles block more progress
[100] Fix | Delete
cycle = self._find_cycle()
[101] Fix | Delete
if cycle:
[102] Fix | Delete
raise CycleError(f"nodes are in a cycle", cycle)
[103] Fix | Delete
[104] Fix | Delete
def get_ready(self):
[105] Fix | Delete
"""Return a tuple of all the nodes that are ready.
[106] Fix | Delete
[107] Fix | Delete
Initially it returns all nodes with no predecessors; once those are marked
[108] Fix | Delete
as processed by calling "done", further calls will return all new nodes that
[109] Fix | Delete
have all their predecessors already processed. Once no more progress can be made,
[110] Fix | Delete
empty tuples are returned.
[111] Fix | Delete
[112] Fix | Delete
Raises ValueError if called without calling "prepare" previously.
[113] Fix | Delete
"""
[114] Fix | Delete
if self._ready_nodes is None:
[115] Fix | Delete
raise ValueError("prepare() must be called first")
[116] Fix | Delete
[117] Fix | Delete
# Get the nodes that are ready and mark them
[118] Fix | Delete
result = tuple(self._ready_nodes)
[119] Fix | Delete
n2i = self._node2info
[120] Fix | Delete
for node in result:
[121] Fix | Delete
n2i[node].npredecessors = _NODE_OUT
[122] Fix | Delete
[123] Fix | Delete
# Clean the list of nodes that are ready and update
[124] Fix | Delete
# the counter of nodes that we have returned.
[125] Fix | Delete
self._ready_nodes.clear()
[126] Fix | Delete
self._npassedout += len(result)
[127] Fix | Delete
[128] Fix | Delete
return result
[129] Fix | Delete
[130] Fix | Delete
def is_active(self):
[131] Fix | Delete
"""Return ``True`` if more progress can be made and ``False`` otherwise.
[132] Fix | Delete
[133] Fix | Delete
Progress can be made if cycles do not block the resolution and either there
[134] Fix | Delete
are still nodes ready that haven't yet been returned by "get_ready" or the
[135] Fix | Delete
number of nodes marked "done" is less than the number that have been returned
[136] Fix | Delete
by "get_ready".
[137] Fix | Delete
[138] Fix | Delete
Raises ValueError if called without calling "prepare" previously.
[139] Fix | Delete
"""
[140] Fix | Delete
if self._ready_nodes is None:
[141] Fix | Delete
raise ValueError("prepare() must be called first")
[142] Fix | Delete
return self._nfinished < self._npassedout or bool(self._ready_nodes)
[143] Fix | Delete
[144] Fix | Delete
def __bool__(self):
[145] Fix | Delete
return self.is_active()
[146] Fix | Delete
[147] Fix | Delete
def done(self, *nodes):
[148] Fix | Delete
"""Marks a set of nodes returned by "get_ready" as processed.
[149] Fix | Delete
[150] Fix | Delete
This method unblocks any successor of each node in *nodes* for being returned
[151] Fix | Delete
in the future by a call to "get_ready".
[152] Fix | Delete
[153] Fix | Delete
Raises :exec:`ValueError` if any node in *nodes* has already been marked as
[154] Fix | Delete
processed by a previous call to this method, if a node was not added to the
[155] Fix | Delete
graph by using "add" or if called without calling "prepare" previously or if
[156] Fix | Delete
node has not yet been returned by "get_ready".
[157] Fix | Delete
"""
[158] Fix | Delete
[159] Fix | Delete
if self._ready_nodes is None:
[160] Fix | Delete
raise ValueError("prepare() must be called first")
[161] Fix | Delete
[162] Fix | Delete
n2i = self._node2info
[163] Fix | Delete
[164] Fix | Delete
for node in nodes:
[165] Fix | Delete
[166] Fix | Delete
# Check if we know about this node (it was added previously using add()
[167] Fix | Delete
if (nodeinfo := n2i.get(node)) is None:
[168] Fix | Delete
raise ValueError(f"node {node!r} was not added using add()")
[169] Fix | Delete
[170] Fix | Delete
# If the node has not being returned (marked as ready) previously, inform the user.
[171] Fix | Delete
stat = nodeinfo.npredecessors
[172] Fix | Delete
if stat != _NODE_OUT:
[173] Fix | Delete
if stat >= 0:
[174] Fix | Delete
raise ValueError(
[175] Fix | Delete
f"node {node!r} was not passed out (still not ready)"
[176] Fix | Delete
)
[177] Fix | Delete
elif stat == _NODE_DONE:
[178] Fix | Delete
raise ValueError(f"node {node!r} was already marked done")
[179] Fix | Delete
else:
[180] Fix | Delete
assert False, f"node {node!r}: unknown status {stat}"
[181] Fix | Delete
[182] Fix | Delete
# Mark the node as processed
[183] Fix | Delete
nodeinfo.npredecessors = _NODE_DONE
[184] Fix | Delete
[185] Fix | Delete
# Go to all the successors and reduce the number of predecessors, collecting all the ones
[186] Fix | Delete
# that are ready to be returned in the next get_ready() call.
[187] Fix | Delete
for successor in nodeinfo.successors:
[188] Fix | Delete
successor_info = n2i[successor]
[189] Fix | Delete
successor_info.npredecessors -= 1
[190] Fix | Delete
if successor_info.npredecessors == 0:
[191] Fix | Delete
self._ready_nodes.append(successor)
[192] Fix | Delete
self._nfinished += 1
[193] Fix | Delete
[194] Fix | Delete
def _find_cycle(self):
[195] Fix | Delete
n2i = self._node2info
[196] Fix | Delete
stack = []
[197] Fix | Delete
itstack = []
[198] Fix | Delete
seen = set()
[199] Fix | Delete
node2stacki = {}
[200] Fix | Delete
[201] Fix | Delete
for node in n2i:
[202] Fix | Delete
if node in seen:
[203] Fix | Delete
continue
[204] Fix | Delete
[205] Fix | Delete
while True:
[206] Fix | Delete
if node in seen:
[207] Fix | Delete
# If we have seen already the node and is in the
[208] Fix | Delete
# current stack we have found a cycle.
[209] Fix | Delete
if node in node2stacki:
[210] Fix | Delete
return stack[node2stacki[node] :] + [node]
[211] Fix | Delete
# else go on to get next successor
[212] Fix | Delete
else:
[213] Fix | Delete
seen.add(node)
[214] Fix | Delete
itstack.append(iter(n2i[node].successors).__next__)
[215] Fix | Delete
node2stacki[node] = len(stack)
[216] Fix | Delete
stack.append(node)
[217] Fix | Delete
[218] Fix | Delete
# Backtrack to the topmost stack entry with
[219] Fix | Delete
# at least another successor.
[220] Fix | Delete
while stack:
[221] Fix | Delete
try:
[222] Fix | Delete
node = itstack[-1]()
[223] Fix | Delete
break
[224] Fix | Delete
except StopIteration:
[225] Fix | Delete
del node2stacki[stack.pop()]
[226] Fix | Delete
itstack.pop()
[227] Fix | Delete
else:
[228] Fix | Delete
break
[229] Fix | Delete
return None
[230] Fix | Delete
[231] Fix | Delete
def static_order(self):
[232] Fix | Delete
"""Returns an iterable of nodes in a topological order.
[233] Fix | Delete
[234] Fix | Delete
The particular order that is returned may depend on the specific
[235] Fix | Delete
order in which the items were inserted in the graph.
[236] Fix | Delete
[237] Fix | Delete
Using this method does not require to call "prepare" or "done". If any
[238] Fix | Delete
cycle is detected, :exc:`CycleError` will be raised.
[239] Fix | Delete
"""
[240] Fix | Delete
self.prepare()
[241] Fix | Delete
while self.is_active():
[242] Fix | Delete
node_group = self.get_ready()
[243] Fix | Delete
yield from node_group
[244] Fix | Delete
self.done(*node_group)
[245] Fix | Delete
[246] Fix | Delete
It is recommended that you Edit text format, this type of Fix handles quite a lot in one request
Function