blob: 2eacd3ab76bf6932c69b18ea57d6d5b456b10787 [file] [log] [blame]
# 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