# Copyright 2015 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Support for directed acyclic graphs. Used in the ResourceGraph model for chrome loading. """ class Node(object): """A node in a DAG. We do not enforce at a node level that a graph is a DAG. Methods like TopologicalSort will assume a DAG and may fail if that's not the case. Nodes are identified with an index that must be unique for a particular graph (it is used for hashing and equality). A graph is represented as a list of nodes, for example in the TopologicalSort class method. By convention a node's index is its position in this list, making it easy to store auxillary information. """ def __init__(self, index): """Create a new node. Args: index: index of the node. We assume these indicies uniquely identify a node (and so use it for hashing and equality). """ self._predecessors = set() self._successors = set() self._index = index def Predecessors(self): return self._predecessors def Successors(self): return self._successors def AddSuccessor(self, s): """Add a successor. Updates appropriate links. Any existing parents of s are unchanged; to move a node you must do a combination of RemoveSuccessor and AddSuccessor. Args: s: the node to add as a successor. """ self._successors.add(s) s._predecessors.add(self) def RemoveSuccessor(self, s): """Removes a successor. Updates appropriate links. Args: s: the node to remove as a successor. Will raise a set exception if s is not an existing successor. """ self._successors.remove(s) s._predecessors.remove(self) def SortedSuccessors(self): children = [c for c in self.Successors()] children.sort(key=lambda c: c.Index()) return children def Index(self): return self._index def __eq__(self, o): return self.Index() == o.Index() def __hash__(self): return hash(self.Index()) def TopologicalSort(nodes, node_filter=None): """Topological sort. We use a BFS-like walk which ensures that sibling are always grouped together in the output. This is more convenient for some later analyses. Args: nodes: [Node, ...] Nodes to sort. node_filter: a filter Node->boolean to restrict the graph. A node passes the filter on a return value of True. Only the subgraph reachable from a root passing the filter is considered. Returns: A list of Nodes in topological order. Note that node indicies are unchanged; the original list nodes is not modified. """ if node_filter is None: node_filter = lambda _: True sorted_nodes = [] sources = [] remaining_in_edges = {} for n in nodes: if n.Predecessors(): remaining_in_edges[n] = len(n.Predecessors()) elif node_filter(n): sources.append(n) while sources: n = sources.pop(0) assert node_filter(n) sorted_nodes.append(n) # We sort by index to get consistent sorts across runs/machines. for c in n.SortedSuccessors(): assert remaining_in_edges[c] > 0 if not node_filter(c): continue remaining_in_edges[c] -= 1 if not remaining_in_edges[c]: sources.append(c) return sorted_nodes