morro
This commit is contained in:
297
sqlalchemy/topological.py
Normal file
297
sqlalchemy/topological.py
Normal file
@@ -0,0 +1,297 @@
|
||||
# topological.py
|
||||
# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer mike_mp@zzzcomputing.com
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Topological sorting algorithms.
|
||||
|
||||
The topological sort is an algorithm that receives this list of
|
||||
dependencies as a *partial ordering*, that is a list of pairs which
|
||||
might say, *X is dependent on Y*, *Q is dependent on Z*, but does not
|
||||
necessarily tell you anything about Q being dependent on X. Therefore,
|
||||
its not a straight sort where every element can be compared to
|
||||
another... only some of the elements have any sorting preference, and
|
||||
then only towards just some of the other elements. For a particular
|
||||
partial ordering, there can be many possible sorts that satisfy the
|
||||
conditions.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.exc import CircularDependencyError
|
||||
from sqlalchemy import util
|
||||
|
||||
__all__ = ['sort', 'sort_with_cycles', 'sort_as_tree']
|
||||
|
||||
def sort(tuples, allitems):
|
||||
"""sort the given list of items by dependency.
|
||||
|
||||
'tuples' is a list of tuples representing a partial ordering.
|
||||
"""
|
||||
|
||||
return [n.item for n in _sort(tuples, allitems, allow_cycles=False, ignore_self_cycles=True)]
|
||||
|
||||
def sort_with_cycles(tuples, allitems):
|
||||
"""sort the given list of items by dependency, cutting out cycles.
|
||||
|
||||
returns results as an iterable of 2-tuples, containing the item,
|
||||
and a list containing items involved in a cycle with this item, if any.
|
||||
|
||||
'tuples' is a list of tuples representing a partial ordering.
|
||||
"""
|
||||
|
||||
return [(n.item, [n.item for n in n.cycles or []]) for n in _sort(tuples, allitems, allow_cycles=True)]
|
||||
|
||||
def sort_as_tree(tuples, allitems, with_cycles=False):
|
||||
"""sort the given list of items by dependency, and return results
|
||||
as a hierarchical tree structure.
|
||||
|
||||
returns results as an iterable of 3-tuples, containing the item,
|
||||
a list containing items involved in a cycle with this item, if any,
|
||||
and a list of child tuples.
|
||||
|
||||
if with_cycles is False, the returned structure is of the same form
|
||||
but the second element of each tuple, i.e. the 'cycles', is an empty list.
|
||||
|
||||
'tuples' is a list of tuples representing a partial ordering.
|
||||
"""
|
||||
|
||||
return _organize_as_tree(_sort(tuples, allitems, allow_cycles=with_cycles))
|
||||
|
||||
|
||||
class _Node(object):
|
||||
"""Represent each item in the sort."""
|
||||
|
||||
def __init__(self, item):
|
||||
self.item = item
|
||||
self.dependencies = set()
|
||||
self.children = []
|
||||
self.cycles = None
|
||||
|
||||
def __str__(self):
|
||||
return self.safestr()
|
||||
|
||||
def safestr(self, indent=0):
|
||||
return (' ' * indent * 2) + \
|
||||
str(self.item) + \
|
||||
(self.cycles is not None and (" (cycles: " + repr([x for x in self.cycles]) + ")") or "") + \
|
||||
"\n" + \
|
||||
''.join(str(n) for n in self.children)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.item)
|
||||
|
||||
def all_deps(self):
|
||||
"""Return a set of dependencies for this node and all its cycles."""
|
||||
|
||||
deps = set(self.dependencies)
|
||||
if self.cycles is not None:
|
||||
for c in self.cycles:
|
||||
deps.update(c.dependencies)
|
||||
return deps
|
||||
|
||||
class _EdgeCollection(object):
|
||||
"""A collection of directed edges."""
|
||||
|
||||
def __init__(self):
|
||||
self.parent_to_children = util.defaultdict(set)
|
||||
self.child_to_parents = util.defaultdict(set)
|
||||
|
||||
def add(self, edge):
|
||||
"""Add an edge to this collection."""
|
||||
|
||||
parentnode, childnode = edge
|
||||
self.parent_to_children[parentnode].add(childnode)
|
||||
self.child_to_parents[childnode].add(parentnode)
|
||||
parentnode.dependencies.add(childnode)
|
||||
|
||||
def remove(self, edge):
|
||||
"""Remove an edge from this collection.
|
||||
|
||||
Return the childnode if it has no other parents.
|
||||
"""
|
||||
|
||||
(parentnode, childnode) = edge
|
||||
self.parent_to_children[parentnode].remove(childnode)
|
||||
self.child_to_parents[childnode].remove(parentnode)
|
||||
if not self.child_to_parents[childnode]:
|
||||
return childnode
|
||||
else:
|
||||
return None
|
||||
|
||||
def has_parents(self, node):
|
||||
return node in self.child_to_parents and bool(self.child_to_parents[node])
|
||||
|
||||
def edges_by_parent(self, node):
|
||||
if node in self.parent_to_children:
|
||||
return [(node, child) for child in self.parent_to_children[node]]
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_parents(self):
|
||||
return self.parent_to_children.keys()
|
||||
|
||||
def pop_node(self, node):
|
||||
"""Remove all edges where the given node is a parent.
|
||||
|
||||
Return the collection of all nodes which were children of the
|
||||
given node, and have no further parents.
|
||||
"""
|
||||
|
||||
children = self.parent_to_children.pop(node, None)
|
||||
if children is not None:
|
||||
for child in children:
|
||||
self.child_to_parents[child].remove(node)
|
||||
if not self.child_to_parents[child]:
|
||||
yield child
|
||||
|
||||
def __len__(self):
|
||||
return sum(len(x) for x in self.parent_to_children.values())
|
||||
|
||||
def __iter__(self):
|
||||
for parent, children in self.parent_to_children.iteritems():
|
||||
for child in children:
|
||||
yield (parent, child)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(list(self))
|
||||
|
||||
def _sort(tuples, allitems, allow_cycles=False, ignore_self_cycles=False):
|
||||
nodes = {}
|
||||
edges = _EdgeCollection()
|
||||
|
||||
for item in list(allitems) + [t[0] for t in tuples] + [t[1] for t in tuples]:
|
||||
item_id = id(item)
|
||||
if item_id not in nodes:
|
||||
nodes[item_id] = _Node(item)
|
||||
|
||||
for t in tuples:
|
||||
id0, id1 = id(t[0]), id(t[1])
|
||||
if t[0] is t[1]:
|
||||
if allow_cycles:
|
||||
n = nodes[id0]
|
||||
n.cycles = set([n])
|
||||
elif not ignore_self_cycles:
|
||||
raise CircularDependencyError("Self-referential dependency detected: %r" % t)
|
||||
continue
|
||||
childnode = nodes[id1]
|
||||
parentnode = nodes[id0]
|
||||
edges.add((parentnode, childnode))
|
||||
|
||||
queue = []
|
||||
for n in nodes.values():
|
||||
if not edges.has_parents(n):
|
||||
queue.append(n)
|
||||
|
||||
output = []
|
||||
while nodes:
|
||||
if not queue:
|
||||
# edges remain but no edgeless nodes to remove; this indicates
|
||||
# a cycle
|
||||
if allow_cycles:
|
||||
for cycle in _find_cycles(edges):
|
||||
lead = cycle[0][0]
|
||||
lead.cycles = set()
|
||||
for edge in cycle:
|
||||
n = edges.remove(edge)
|
||||
lead.cycles.add(edge[0])
|
||||
lead.cycles.add(edge[1])
|
||||
if n is not None:
|
||||
queue.append(n)
|
||||
for n in lead.cycles:
|
||||
if n is not lead:
|
||||
n._cyclical = True
|
||||
for (n, k) in list(edges.edges_by_parent(n)):
|
||||
edges.add((lead, k))
|
||||
edges.remove((n, k))
|
||||
continue
|
||||
else:
|
||||
# long cycles not allowed
|
||||
raise CircularDependencyError("Circular dependency detected: %r %r " % (edges, queue))
|
||||
node = queue.pop()
|
||||
if not hasattr(node, '_cyclical'):
|
||||
output.append(node)
|
||||
del nodes[id(node.item)]
|
||||
for childnode in edges.pop_node(node):
|
||||
queue.append(childnode)
|
||||
return output
|
||||
|
||||
def _organize_as_tree(nodes):
|
||||
"""Given a list of nodes from a topological sort, organize the
|
||||
nodes into a tree structure, with as many non-dependent nodes
|
||||
set as siblings to each other as possible.
|
||||
|
||||
returns nodes as 3-tuples (item, cycles, children).
|
||||
"""
|
||||
|
||||
if not nodes:
|
||||
return None
|
||||
# a list of all currently independent subtrees as a tuple of
|
||||
# (root_node, set_of_all_tree_nodes, set_of_all_cycle_nodes_in_tree)
|
||||
# order of the list has no semantics for the algorithmic
|
||||
independents = []
|
||||
# in reverse topological order
|
||||
for node in reversed(nodes):
|
||||
# nodes subtree and cycles contain the node itself
|
||||
subtree = set([node])
|
||||
if node.cycles is not None:
|
||||
cycles = set(node.cycles)
|
||||
else:
|
||||
cycles = set()
|
||||
# get a set of dependent nodes of node and its cycles
|
||||
nodealldeps = node.all_deps()
|
||||
if nodealldeps:
|
||||
# iterate over independent node indexes in reverse order so we can efficiently remove them
|
||||
for index in xrange(len(independents) - 1, -1, -1):
|
||||
child, childsubtree, childcycles = independents[index]
|
||||
# if there is a dependency between this node and an independent node
|
||||
if (childsubtree.intersection(nodealldeps) or childcycles.intersection(node.dependencies)):
|
||||
# prepend child to nodes children
|
||||
# (append should be fine, but previous implemetation used prepend)
|
||||
node.children[0:0] = [(child.item, [n.item for n in child.cycles or []], child.children)]
|
||||
# merge childs subtree and cycles
|
||||
subtree.update(childsubtree)
|
||||
cycles.update(childcycles)
|
||||
# remove the child from list of independent subtrees
|
||||
independents[index:index+1] = []
|
||||
# add node as a new independent subtree
|
||||
independents.append((node, subtree, cycles))
|
||||
# choose an arbitrary node from list of all independent subtrees
|
||||
head = independents.pop()[0]
|
||||
# add all other independent subtrees as a child of the chosen root
|
||||
# used prepend [0:0] instead of extend to maintain exact behaviour of previous implementation
|
||||
head.children[0:0] = [(i[0].item, [n.item for n in i[0].cycles or []], i[0].children) for i in independents]
|
||||
return (head.item, [n.item for n in head.cycles or []], head.children)
|
||||
|
||||
def _find_cycles(edges):
|
||||
cycles = {}
|
||||
|
||||
def traverse(node, cycle, goal):
|
||||
for (n, key) in edges.edges_by_parent(node):
|
||||
if key in cycle:
|
||||
continue
|
||||
cycle.add(key)
|
||||
if key is goal:
|
||||
cycset = set(cycle)
|
||||
for x in cycle:
|
||||
if x in cycles:
|
||||
existing_set = cycles[x]
|
||||
existing_set.update(cycset)
|
||||
for y in existing_set:
|
||||
cycles[y] = existing_set
|
||||
cycset = existing_set
|
||||
else:
|
||||
cycles[x] = cycset
|
||||
else:
|
||||
traverse(key, cycle, goal)
|
||||
cycle.pop()
|
||||
|
||||
for parent in edges.get_parents():
|
||||
traverse(parent, set(), parent)
|
||||
|
||||
unique_cycles = set(tuple(s) for s in cycles.values())
|
||||
|
||||
for cycle in unique_cycles:
|
||||
edgecollection = [edge for edge in edges
|
||||
if edge[0] in cycle and edge[1] in cycle]
|
||||
yield edgecollection
|
||||
Reference in New Issue
Block a user