17db96d56Sopenharmony_ci:mod:`graphlib` --- Functionality to operate with graph-like structures 27db96d56Sopenharmony_ci========================================================================= 37db96d56Sopenharmony_ci 47db96d56Sopenharmony_ci.. module:: graphlib 57db96d56Sopenharmony_ci :synopsis: Functionality to operate with graph-like structures 67db96d56Sopenharmony_ci 77db96d56Sopenharmony_ci 87db96d56Sopenharmony_ci**Source code:** :source:`Lib/graphlib.py` 97db96d56Sopenharmony_ci 107db96d56Sopenharmony_ci.. testsetup:: default 117db96d56Sopenharmony_ci 127db96d56Sopenharmony_ci import graphlib 137db96d56Sopenharmony_ci from graphlib import * 147db96d56Sopenharmony_ci 157db96d56Sopenharmony_ci-------------- 167db96d56Sopenharmony_ci 177db96d56Sopenharmony_ci 187db96d56Sopenharmony_ci.. class:: TopologicalSorter(graph=None) 197db96d56Sopenharmony_ci 207db96d56Sopenharmony_ci Provides functionality to topologically sort a graph of :term:`hashable` nodes. 217db96d56Sopenharmony_ci 227db96d56Sopenharmony_ci A topological order is a linear ordering of the vertices in a graph such that 237db96d56Sopenharmony_ci for every directed edge u -> v from vertex u to vertex v, vertex u comes 247db96d56Sopenharmony_ci before vertex v in the ordering. For instance, the vertices of the graph may 257db96d56Sopenharmony_ci represent tasks to be performed, and the edges may represent constraints that 267db96d56Sopenharmony_ci one task must be performed before another; in this example, a topological 277db96d56Sopenharmony_ci ordering is just a valid sequence for the tasks. A complete topological 287db96d56Sopenharmony_ci ordering is possible if and only if the graph has no directed cycles, that 297db96d56Sopenharmony_ci is, if it is a directed acyclic graph. 307db96d56Sopenharmony_ci 317db96d56Sopenharmony_ci If the optional *graph* argument is provided it must be a dictionary 327db96d56Sopenharmony_ci representing a directed acyclic graph where the keys are nodes and the values 337db96d56Sopenharmony_ci are iterables of all predecessors of that node in the graph (the nodes that 347db96d56Sopenharmony_ci have edges that point to the value in the key). Additional nodes can be added 357db96d56Sopenharmony_ci to the graph using the :meth:`~TopologicalSorter.add` method. 367db96d56Sopenharmony_ci 377db96d56Sopenharmony_ci In the general case, the steps required to perform the sorting of a given 387db96d56Sopenharmony_ci graph are as follows: 397db96d56Sopenharmony_ci 407db96d56Sopenharmony_ci * Create an instance of the :class:`TopologicalSorter` with an optional 417db96d56Sopenharmony_ci initial graph. 427db96d56Sopenharmony_ci * Add additional nodes to the graph. 437db96d56Sopenharmony_ci * Call :meth:`~TopologicalSorter.prepare` on the graph. 447db96d56Sopenharmony_ci * While :meth:`~TopologicalSorter.is_active` is ``True``, iterate over 457db96d56Sopenharmony_ci the nodes returned by :meth:`~TopologicalSorter.get_ready` and 467db96d56Sopenharmony_ci process them. Call :meth:`~TopologicalSorter.done` on each node as it 477db96d56Sopenharmony_ci finishes processing. 487db96d56Sopenharmony_ci 497db96d56Sopenharmony_ci In case just an immediate sorting of the nodes in the graph is required and 507db96d56Sopenharmony_ci no parallelism is involved, the convenience method 517db96d56Sopenharmony_ci :meth:`TopologicalSorter.static_order` can be used directly: 527db96d56Sopenharmony_ci 537db96d56Sopenharmony_ci .. doctest:: 547db96d56Sopenharmony_ci 557db96d56Sopenharmony_ci >>> graph = {"D": {"B", "C"}, "C": {"A"}, "B": {"A"}} 567db96d56Sopenharmony_ci >>> ts = TopologicalSorter(graph) 577db96d56Sopenharmony_ci >>> tuple(ts.static_order()) 587db96d56Sopenharmony_ci ('A', 'C', 'B', 'D') 597db96d56Sopenharmony_ci 607db96d56Sopenharmony_ci The class is designed to easily support parallel processing of the nodes as 617db96d56Sopenharmony_ci they become ready. For instance:: 627db96d56Sopenharmony_ci 637db96d56Sopenharmony_ci topological_sorter = TopologicalSorter() 647db96d56Sopenharmony_ci 657db96d56Sopenharmony_ci # Add nodes to 'topological_sorter'... 667db96d56Sopenharmony_ci 677db96d56Sopenharmony_ci topological_sorter.prepare() 687db96d56Sopenharmony_ci while topological_sorter.is_active(): 697db96d56Sopenharmony_ci for node in topological_sorter.get_ready(): 707db96d56Sopenharmony_ci # Worker threads or processes take nodes to work on off the 717db96d56Sopenharmony_ci # 'task_queue' queue. 727db96d56Sopenharmony_ci task_queue.put(node) 737db96d56Sopenharmony_ci 747db96d56Sopenharmony_ci # When the work for a node is done, workers put the node in 757db96d56Sopenharmony_ci # 'finalized_tasks_queue' so we can get more nodes to work on. 767db96d56Sopenharmony_ci # The definition of 'is_active()' guarantees that, at this point, at 777db96d56Sopenharmony_ci # least one node has been placed on 'task_queue' that hasn't yet 787db96d56Sopenharmony_ci # been passed to 'done()', so this blocking 'get()' must (eventually) 797db96d56Sopenharmony_ci # succeed. After calling 'done()', we loop back to call 'get_ready()' 807db96d56Sopenharmony_ci # again, so put newly freed nodes on 'task_queue' as soon as 817db96d56Sopenharmony_ci # logically possible. 827db96d56Sopenharmony_ci node = finalized_tasks_queue.get() 837db96d56Sopenharmony_ci topological_sorter.done(node) 847db96d56Sopenharmony_ci 857db96d56Sopenharmony_ci .. method:: add(node, *predecessors) 867db96d56Sopenharmony_ci 877db96d56Sopenharmony_ci Add a new node and its predecessors to the graph. Both the *node* and all 887db96d56Sopenharmony_ci elements in *predecessors* must be :term:`hashable`. 897db96d56Sopenharmony_ci 907db96d56Sopenharmony_ci If called multiple times with the same node argument, the set of 917db96d56Sopenharmony_ci dependencies will be the union of all dependencies passed in. 927db96d56Sopenharmony_ci 937db96d56Sopenharmony_ci It is possible to add a node with no dependencies (*predecessors* is not 947db96d56Sopenharmony_ci provided) or to provide a dependency twice. If a node that has not been 957db96d56Sopenharmony_ci provided before is included among *predecessors* it will be automatically 967db96d56Sopenharmony_ci added to the graph with no predecessors of its own. 977db96d56Sopenharmony_ci 987db96d56Sopenharmony_ci Raises :exc:`ValueError` if called after :meth:`~TopologicalSorter.prepare`. 997db96d56Sopenharmony_ci 1007db96d56Sopenharmony_ci .. method:: prepare() 1017db96d56Sopenharmony_ci 1027db96d56Sopenharmony_ci Mark the graph as finished and check for cycles in the graph. If any cycle 1037db96d56Sopenharmony_ci is detected, :exc:`CycleError` will be raised, but 1047db96d56Sopenharmony_ci :meth:`~TopologicalSorter.get_ready` can still be used to obtain as many 1057db96d56Sopenharmony_ci nodes as possible until cycles block more progress. After a call to this 1067db96d56Sopenharmony_ci function, the graph cannot be modified, and therefore no more nodes can be 1077db96d56Sopenharmony_ci added using :meth:`~TopologicalSorter.add`. 1087db96d56Sopenharmony_ci 1097db96d56Sopenharmony_ci .. method:: is_active() 1107db96d56Sopenharmony_ci 1117db96d56Sopenharmony_ci Returns ``True`` if more progress can be made and ``False`` otherwise. 1127db96d56Sopenharmony_ci Progress can be made if cycles do not block the resolution and either 1137db96d56Sopenharmony_ci there are still nodes ready that haven't yet been returned by 1147db96d56Sopenharmony_ci :meth:`TopologicalSorter.get_ready` or the number of nodes marked 1157db96d56Sopenharmony_ci :meth:`TopologicalSorter.done` is less than the number that have been 1167db96d56Sopenharmony_ci returned by :meth:`TopologicalSorter.get_ready`. 1177db96d56Sopenharmony_ci 1187db96d56Sopenharmony_ci The :meth:`~TopologicalSorter.__bool__` method of this class defers to 1197db96d56Sopenharmony_ci this function, so instead of:: 1207db96d56Sopenharmony_ci 1217db96d56Sopenharmony_ci if ts.is_active(): 1227db96d56Sopenharmony_ci ... 1237db96d56Sopenharmony_ci 1247db96d56Sopenharmony_ci it is possible to simply do:: 1257db96d56Sopenharmony_ci 1267db96d56Sopenharmony_ci if ts: 1277db96d56Sopenharmony_ci ... 1287db96d56Sopenharmony_ci 1297db96d56Sopenharmony_ci Raises :exc:`ValueError` if called without calling 1307db96d56Sopenharmony_ci :meth:`~TopologicalSorter.prepare` previously. 1317db96d56Sopenharmony_ci 1327db96d56Sopenharmony_ci .. method:: done(*nodes) 1337db96d56Sopenharmony_ci 1347db96d56Sopenharmony_ci Marks a set of nodes returned by :meth:`TopologicalSorter.get_ready` as 1357db96d56Sopenharmony_ci processed, unblocking any successor of each node in *nodes* for being 1367db96d56Sopenharmony_ci returned in the future by a call to :meth:`TopologicalSorter.get_ready`. 1377db96d56Sopenharmony_ci 1387db96d56Sopenharmony_ci Raises :exc:`ValueError` if any node in *nodes* has already been marked as 1397db96d56Sopenharmony_ci processed by a previous call to this method or if a node was not added to 1407db96d56Sopenharmony_ci the graph by using :meth:`TopologicalSorter.add`, if called without 1417db96d56Sopenharmony_ci calling :meth:`~TopologicalSorter.prepare` or if node has not yet been 1427db96d56Sopenharmony_ci returned by :meth:`~TopologicalSorter.get_ready`. 1437db96d56Sopenharmony_ci 1447db96d56Sopenharmony_ci .. method:: get_ready() 1457db96d56Sopenharmony_ci 1467db96d56Sopenharmony_ci Returns a ``tuple`` with all the nodes that are ready. Initially it 1477db96d56Sopenharmony_ci returns all nodes with no predecessors, and once those are marked as 1487db96d56Sopenharmony_ci processed by calling :meth:`TopologicalSorter.done`, further calls will 1497db96d56Sopenharmony_ci return all new nodes that have all their predecessors already processed. 1507db96d56Sopenharmony_ci Once no more progress can be made, empty tuples are returned. 1517db96d56Sopenharmony_ci 1527db96d56Sopenharmony_ci Raises :exc:`ValueError` if called without calling 1537db96d56Sopenharmony_ci :meth:`~TopologicalSorter.prepare` previously. 1547db96d56Sopenharmony_ci 1557db96d56Sopenharmony_ci .. method:: static_order() 1567db96d56Sopenharmony_ci 1577db96d56Sopenharmony_ci Returns an iterator object which will iterate over nodes in a topological 1587db96d56Sopenharmony_ci order. When using this method, :meth:`~TopologicalSorter.prepare` and 1597db96d56Sopenharmony_ci :meth:`~TopologicalSorter.done` should not be called. This method is 1607db96d56Sopenharmony_ci equivalent to:: 1617db96d56Sopenharmony_ci 1627db96d56Sopenharmony_ci def static_order(self): 1637db96d56Sopenharmony_ci self.prepare() 1647db96d56Sopenharmony_ci while self.is_active(): 1657db96d56Sopenharmony_ci node_group = self.get_ready() 1667db96d56Sopenharmony_ci yield from node_group 1677db96d56Sopenharmony_ci self.done(*node_group) 1687db96d56Sopenharmony_ci 1697db96d56Sopenharmony_ci The particular order that is returned may depend on the specific order in 1707db96d56Sopenharmony_ci which the items were inserted in the graph. For example: 1717db96d56Sopenharmony_ci 1727db96d56Sopenharmony_ci .. doctest:: 1737db96d56Sopenharmony_ci 1747db96d56Sopenharmony_ci >>> ts = TopologicalSorter() 1757db96d56Sopenharmony_ci >>> ts.add(3, 2, 1) 1767db96d56Sopenharmony_ci >>> ts.add(1, 0) 1777db96d56Sopenharmony_ci >>> print([*ts.static_order()]) 1787db96d56Sopenharmony_ci [2, 0, 1, 3] 1797db96d56Sopenharmony_ci 1807db96d56Sopenharmony_ci >>> ts2 = TopologicalSorter() 1817db96d56Sopenharmony_ci >>> ts2.add(1, 0) 1827db96d56Sopenharmony_ci >>> ts2.add(3, 2, 1) 1837db96d56Sopenharmony_ci >>> print([*ts2.static_order()]) 1847db96d56Sopenharmony_ci [0, 2, 1, 3] 1857db96d56Sopenharmony_ci 1867db96d56Sopenharmony_ci This is due to the fact that "0" and "2" are in the same level in the 1877db96d56Sopenharmony_ci graph (they would have been returned in the same call to 1887db96d56Sopenharmony_ci :meth:`~TopologicalSorter.get_ready`) and the order between them is 1897db96d56Sopenharmony_ci determined by the order of insertion. 1907db96d56Sopenharmony_ci 1917db96d56Sopenharmony_ci 1927db96d56Sopenharmony_ci If any cycle is detected, :exc:`CycleError` will be raised. 1937db96d56Sopenharmony_ci 1947db96d56Sopenharmony_ci .. versionadded:: 3.9 1957db96d56Sopenharmony_ci 1967db96d56Sopenharmony_ci 1977db96d56Sopenharmony_ciExceptions 1987db96d56Sopenharmony_ci---------- 1997db96d56Sopenharmony_ciThe :mod:`graphlib` module defines the following exception classes: 2007db96d56Sopenharmony_ci 2017db96d56Sopenharmony_ci.. exception:: CycleError 2027db96d56Sopenharmony_ci 2037db96d56Sopenharmony_ci Subclass of :exc:`ValueError` raised by :meth:`TopologicalSorter.prepare` if cycles exist 2047db96d56Sopenharmony_ci in the working graph. If multiple cycles exist, only one undefined choice among them will 2057db96d56Sopenharmony_ci be reported and included in the exception. 2067db96d56Sopenharmony_ci 2077db96d56Sopenharmony_ci The detected cycle can be accessed via the second element in the :attr:`~CycleError.args` 2087db96d56Sopenharmony_ci attribute of the exception instance and consists in a list of nodes, such that each node is, 2097db96d56Sopenharmony_ci in the graph, an immediate predecessor of the next node in the list. In the reported list, 2107db96d56Sopenharmony_ci the first and the last node will be the same, to make it clear that it is cyclic. 211