|
| 1 | +####################################################################### |
| 2 | +# Implements a topological sort algorithm. |
| 3 | +# https://bitbucket.org/ericvsmith/toposort/src/25b5894c4229cb888f77cf0c077c05e2464446ac/toposort.py |
| 4 | +# |
| 5 | +# Copyright 2014 True Blade Systems, Inc. |
| 6 | +# |
| 7 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | +# you may not use this file except in compliance with the License. |
| 9 | +# You may obtain a copy of the License at |
| 10 | +# |
| 11 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | +# |
| 13 | +# Unless required by applicable law or agreed to in writing, software |
| 14 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | +# See the License for the specific language governing permissions and |
| 17 | +# limitations under the License. |
| 18 | +# |
| 19 | +######################################################################## |
| 20 | + |
| 21 | +from functools import reduce as _reduce |
| 22 | + |
| 23 | + |
| 24 | +__all__ = ['toposort', 'toposort_flatten'] |
| 25 | + |
| 26 | + |
| 27 | +def toposort(data): |
| 28 | + """Dependencies are expressed as a dictionary whose keys are items |
| 29 | +and whose values are a set of dependent items. Output is a list of |
| 30 | +sets in topological order. The first set consists of items with no |
| 31 | +dependences, each subsequent set consists of items that depend upon |
| 32 | +items in the preceeding sets. |
| 33 | +""" |
| 34 | + |
| 35 | + # Special case empty input. |
| 36 | + if len(data) == 0: |
| 37 | + return |
| 38 | + |
| 39 | + # Copy the input so as to leave it unmodified. |
| 40 | + data = data.copy() |
| 41 | + |
| 42 | + # Ignore self dependencies. |
| 43 | + for k, v in data.items(): |
| 44 | + v.discard(k) |
| 45 | + # Find all items that don't depend on anything. |
| 46 | + extra_items_in_deps = _reduce(set.union, data.values()) - set(data.keys()) |
| 47 | + # Add empty dependences where needed. |
| 48 | + data.update({item:set() for item in extra_items_in_deps}) |
| 49 | + while True: |
| 50 | + ordered = set(item for item, dep in data.items() if len(dep) == 0) |
| 51 | + if not ordered: |
| 52 | + break |
| 53 | + yield ordered |
| 54 | + data = {item: (dep - ordered) |
| 55 | + for item, dep in data.items() |
| 56 | + if item not in ordered} |
| 57 | + if len(data) != 0: |
| 58 | + raise ValueError('Cyclic dependencies exist among these items: {}'.format(', '.join(repr(x) for x in data.items()))) |
| 59 | + |
| 60 | + |
| 61 | +def toposort_flatten(data, sort=True): |
| 62 | + """Returns a single list of dependencies. For any set returned by |
| 63 | +toposort(), those items are sorted and appended to the result (just to |
| 64 | +make the results deterministic).""" |
| 65 | + |
| 66 | + result = [] |
| 67 | + for d in toposort(data): |
| 68 | + try: |
| 69 | + result.extend((sorted if sort else list)(d)) |
| 70 | + except TypeError as e: |
| 71 | + result.extend(list(d)) |
| 72 | + return result |
0 commit comments