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