|
1
|
1 |
|
import operator |
|
2
|
1 |
|
import itertools |
|
3
|
|
|
|
|
4
|
1 |
|
from ppp_datamodel.nodes import Resource |
|
5
|
1 |
|
from ppp_datamodel.nodes.list_operators import * |
|
6
|
|
|
|
|
7
|
1 |
|
__all__ = ['simplify'] |
|
8
|
|
|
|
|
9
|
1 |
|
def partition(pred, list_): |
|
10
|
|
|
# Partition the items in lists / non-lists |
|
11
|
1 |
|
list_ = [x for x in list_ if x] |
|
12
|
1 |
|
lists = filter(pred, list_) |
|
13
|
1 |
|
not_lists = itertools.filterfalse(pred, list_) |
|
14
|
1 |
|
return (lists, not_lists) |
|
15
|
|
|
|
|
16
|
1 |
|
def simplify_union(tree): |
|
17
|
|
|
# Trivial cases |
|
18
|
1 |
|
if len(tree.list) == 0: |
|
19
|
1 |
|
return List([]) |
|
20
|
1 |
|
elif len(tree.list) == 1: |
|
21
|
1 |
|
return tree.list[0] |
|
22
|
|
|
|
|
23
|
1 |
|
(lists, non_lists) = partition(lambda x:isinstance(x, (List, Resource)), |
|
24
|
|
|
tree.list) |
|
25
|
|
|
# Make union of lists |
|
26
|
1 |
|
lists = [x.list if isinstance(x, List) else [x] for x in lists] |
|
27
|
1 |
|
lists = list(set(itertools.chain(*lists))) |
|
28
|
|
|
|
|
29
|
1 |
|
non_lists = list(non_lists) |
|
30
|
1 |
|
if lists: |
|
31
|
1 |
|
all_ = non_lists |
|
32
|
1 |
|
all_.append(List(lists)) |
|
33
|
1 |
|
return Union(all_) |
|
34
|
|
|
elif len(non_lists) == 1: |
|
35
|
|
|
return non_lists |
|
36
|
|
|
else: |
|
37
|
|
|
return Union(non_lists) |
|
38
|
|
|
|
|
39
|
1 |
|
def simplify_intersection(tree): |
|
40
|
|
|
# Trivial cases |
|
41
|
1 |
|
if len(tree.list) == 0: |
|
42
|
1 |
|
return tree |
|
43
|
1 |
|
elif len(tree.list) == 1: |
|
44
|
1 |
|
return tree.list[0] |
|
45
|
|
|
|
|
46
|
1 |
|
(a, b) = partition(lambda x:isinstance(x, (List, Resource)), tree.list) |
|
47
|
1 |
|
(lists, non_lists) = partition(lambda x:isinstance(x, (List, Resource)), |
|
48
|
|
|
tree.list) |
|
49
|
1 |
|
lists = list(lists) |
|
50
|
|
|
# Make intersection of lists |
|
51
|
1 |
|
try: |
|
52
|
|
|
# Efficient algorithm if everything is hashable |
|
53
|
1 |
|
lists = [set(x.list) if isinstance(x, List) else {x} for x in lists] or [set()] |
|
54
|
1 |
|
lists = list(lists[0].intersection(*lists[1:])) |
|
55
|
1 |
|
except TypeError: |
|
56
|
|
|
# If there is a non-hashable value, fallback to a less efficient algorithm |
|
57
|
1 |
|
lists = [x.list if isinstance(x, List) else [x] for x in lists] or [[]] |
|
58
|
1 |
|
intersected_lists = list(lists[0]) |
|
59
|
1 |
|
for l in lists[1:]: |
|
60
|
1 |
|
intersected_lists = [x for x in intersected_lists if x in l] |
|
61
|
1 |
|
lists = intersected_lists |
|
62
|
|
|
|
|
63
|
1 |
|
non_lists = list(non_lists) |
|
64
|
1 |
|
if lists: |
|
65
|
1 |
|
all_ = non_lists |
|
66
|
1 |
|
all_.append(simplify_list(List(lists))) |
|
67
|
1 |
|
return Intersection(all_) |
|
68
|
|
|
elif len(non_lists) == 1: |
|
69
|
|
|
return non_lists |
|
70
|
|
|
else: |
|
71
|
|
|
return Intersection(non_lists) |
|
72
|
|
|
|
|
73
|
1 |
|
def simplify_list(tree): |
|
74
|
1 |
|
if len(tree.list) == 1: |
|
75
|
1 |
|
return tree.list[0] |
|
76
|
|
|
else: |
|
77
|
1 |
|
return tree |
|
78
|
|
|
|
|
79
|
1 |
|
predicates = { |
|
80
|
|
|
Union: simplify_union, |
|
81
|
|
|
Intersection: simplify_intersection, |
|
82
|
|
|
List: simplify_list, |
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
1 |
|
def predicate(tree): |
|
86
|
1 |
|
for (cls, f) in predicates.items(): |
|
87
|
1 |
|
if isinstance(tree, cls): |
|
88
|
1 |
|
return f(tree) |
|
89
|
1 |
|
return tree |
|
90
|
|
|
|
|
91
|
1 |
|
def simplify(tree): |
|
92
|
1 |
|
old_tree = None |
|
93
|
1 |
|
while old_tree != tree: |
|
94
|
1 |
|
old_tree = tree |
|
95
|
1 |
|
tree = tree.traverse(predicate) |
|
96
|
|
|
return tree |
|
97
|
|
|
|