topological.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. # util/topological.py
  2. # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. """Topological sorting algorithms."""
  8. from ..exc import CircularDependencyError
  9. from .. import util
  10. __all__ = ['sort', 'sort_as_subsets', 'find_cycles']
  11. def sort_as_subsets(tuples, allitems, deterministic_order=False):
  12. edges = util.defaultdict(set)
  13. for parent, child in tuples:
  14. edges[child].add(parent)
  15. Set = util.OrderedSet if deterministic_order else set
  16. todo = Set(allitems)
  17. while todo:
  18. output = Set()
  19. for node in todo:
  20. if todo.isdisjoint(edges[node]):
  21. output.add(node)
  22. if not output:
  23. raise CircularDependencyError(
  24. "Circular dependency detected.",
  25. find_cycles(tuples, allitems),
  26. _gen_edges(edges)
  27. )
  28. todo.difference_update(output)
  29. yield output
  30. def sort(tuples, allitems, deterministic_order=False):
  31. """sort the given list of items by dependency.
  32. 'tuples' is a list of tuples representing a partial ordering.
  33. 'deterministic_order' keeps items within a dependency tier in list order.
  34. """
  35. for set_ in sort_as_subsets(tuples, allitems, deterministic_order):
  36. for s in set_:
  37. yield s
  38. def find_cycles(tuples, allitems):
  39. # adapted from:
  40. # http://neopythonic.blogspot.com/2009/01/detecting-cycles-in-directed-graph.html
  41. edges = util.defaultdict(set)
  42. for parent, child in tuples:
  43. edges[parent].add(child)
  44. nodes_to_test = set(edges)
  45. output = set()
  46. # we'd like to find all nodes that are
  47. # involved in cycles, so we do the full
  48. # pass through the whole thing for each
  49. # node in the original list.
  50. # we can go just through parent edge nodes.
  51. # if a node is only a child and never a parent,
  52. # by definition it can't be part of a cycle. same
  53. # if it's not in the edges at all.
  54. for node in nodes_to_test:
  55. stack = [node]
  56. todo = nodes_to_test.difference(stack)
  57. while stack:
  58. top = stack[-1]
  59. for node in edges[top]:
  60. if node in stack:
  61. cyc = stack[stack.index(node):]
  62. todo.difference_update(cyc)
  63. output.update(cyc)
  64. if node in todo:
  65. stack.append(node)
  66. todo.remove(node)
  67. break
  68. else:
  69. node = stack.pop()
  70. return output
  71. def _gen_edges(edges):
  72. return set([
  73. (right, left)
  74. for left in edges
  75. for right in edges[left]
  76. ])