|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2004 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing some graphical utility functions. |
|
8 """ |
|
9 |
|
10 |
|
11 from __future__ import unicode_literals |
|
12 |
|
13 |
|
14 class RecursionError(OverflowError, ValueError): |
|
15 """ |
|
16 Unable to calculate result because of recursive structure. |
|
17 """ |
|
18 pass |
|
19 |
|
20 |
|
21 def sort(nodes, routes, noRecursion=False): |
|
22 """ |
|
23 Function to sort widgets topographically. |
|
24 |
|
25 Passed a list of nodes and a list of source, dest routes, it attempts |
|
26 to create a list of stages, where each sub list is one stage in a process. |
|
27 |
|
28 The algorithm was taken from Boa Constructor. |
|
29 |
|
30 @param nodes list of nodes to be sorted |
|
31 @param routes list of routes between the nodes |
|
32 @param noRecursion flag indicating, if recursion errors should be raised |
|
33 @exception RecursionError a recursion error was detected |
|
34 @return list of stages |
|
35 """ |
|
36 children, parents = _buildChildrenLists(routes) |
|
37 |
|
38 # first stage is those nodes having no incoming routes... |
|
39 stage = [] |
|
40 stages = [stage] |
|
41 taken = [] |
|
42 for node in nodes: |
|
43 if not parents.get(node): |
|
44 stage.append(node) |
|
45 |
|
46 if nodes and not stage: |
|
47 # there is no element, which does not depend on some other element! |
|
48 stage.append(nodes[0]) |
|
49 |
|
50 taken.extend(stage) |
|
51 nodes = list(filter(lambda x, li=stage: x not in li, nodes)) |
|
52 while nodes: |
|
53 previousStageChildren = [] |
|
54 nodelen = len(nodes) |
|
55 |
|
56 # second stage are those nodes, which are direct children of the |
|
57 # first stage |
|
58 for node in stage: |
|
59 for child in children.get(node, []): |
|
60 if child not in previousStageChildren and child not in taken: |
|
61 previousStageChildren.append(child) |
|
62 elif child in taken and noRecursion: |
|
63 raise RecursionError((child, node)) |
|
64 |
|
65 # unless they are children of other direct children... |
|
66 stage = previousStageChildren |
|
67 removes = [] |
|
68 for current in stage: |
|
69 currentParents = parents.get(current, []) |
|
70 for parent in currentParents: |
|
71 if parent in stage and parent != current: |
|
72 # might wind up removing current |
|
73 if current not in parents.get(parent, []): |
|
74 # is not mutually dependant |
|
75 removes.append(current) |
|
76 |
|
77 for remove in removes: |
|
78 while remove in stage: |
|
79 stage.remove(remove) |
|
80 |
|
81 stages.append(stage) |
|
82 taken.extend(stage) |
|
83 nodes = list(filter(lambda x, li=stage: x not in li, nodes)) |
|
84 if nodelen == len(nodes): |
|
85 if noRecursion: |
|
86 raise RecursionError(nodes) |
|
87 else: |
|
88 stages.append(nodes[:]) |
|
89 nodes = [] |
|
90 |
|
91 return stages |
|
92 |
|
93 |
|
94 def _buildChildrenLists(routes): |
|
95 """ |
|
96 Function to build up parent - child relationships. |
|
97 |
|
98 Taken from Boa Constructor. |
|
99 |
|
100 @param routes list of routes between nodes |
|
101 @return dictionary of child and dictionary of parent relationships |
|
102 """ |
|
103 childrenTable = {} |
|
104 parentTable = {} |
|
105 for sourceID, destinationID in routes: |
|
106 currentChildren = childrenTable.get(sourceID, []) |
|
107 currentParents = parentTable.get(destinationID, []) |
|
108 if destinationID not in currentChildren: |
|
109 currentChildren.append(destinationID) |
|
110 if sourceID not in currentParents: |
|
111 currentParents.append(sourceID) |
|
112 childrenTable[sourceID] = currentChildren |
|
113 parentTable[destinationID] = currentParents |
|
114 return childrenTable, parentTable |