1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
__docformat__ = 'restructuredtext'
from pattern import Pattern
class Category:
"""
A Category is a "set" of states or events. The category specifies a name,
certain arguments that must be provided, and constraints for the values of
those arguments. Any possible state/event which meets these restrictions is
in the category.
"""
def __init__(self, name, **args):
"""
Categories are constructed with a `name` and a series of keyword
arguments. `name` is a string. Each keyword argument that is specified
is the name of an argument a state or event must posess to be considered
to be a member of the category. The value of the keyword arguments are
patterns that must match the value of the argument.
"""
self.args = args
self.name = name
def superset_of(self, other):
"""
Determine if this category is a superset of `other`.
"""
if self.name != other.name: return False
for key, value in self.args.iteritems():
if not other.args.has_key(key): return False
if not other.args[key].intersect(value) == other.args[key]: return False
return True
def subtract(self, other):
"""
returns a set of categories that, when combined, include all items
that are in `self` but *not* in `other`
"""
retval = set()
for key, value in other.args.iteritems():
args = {}
for k, v in self.args.iteritems(): args[k] = v
args[key].intersect(value.complement())
if args[key].nonempty():
retval.add(Category(self.name, **args))
def inverse_set(self):
"""
Returns a set of Categories that will match any item NOT matched by
this one (provided it specifies the same or more arguments).
"""
retval = set()
for key, value in self.args.iteritems():
value = value.complement()
if not value.nonempty():
continue
newargs = self.args.copy()
newargs[key] = value
retval.add(Category(self.name, **newargs))
return retval
def intersect_args(self, **args):
"""
`intersect` this Category with a new Category that has the same name and
the provided args.
"""
return self.intersect(Category(self.name, **args))
def intersect(self, other):
"""
Returns a new category consisting only of items that are in this
category, and the category `other`.
"""
if self.name != other.name: return None
args = {}
for key in set(self.args.keys() + other.args.keys()):
if not self.args.has_key(key):
args[key] = other.args[key]
elif not other.args.has_key(key):
args[key] = self.args[key]
else:
val = self.args[key].intersect(other.args[key])
if not val.nonempty():
return None
args[key] = val
return Category(self.name, **args)
def __str__(self):
return "%s(%s)" % (
self.name,
", ".join(["%s: %s" % (key, val) \
for key, val in self.args.iteritems()])
)
def __repr__(self):
return self.__str__()
def __hash__(self):
return hash((self.argstup(), self.name))
def __eq__(self, other):
if not other.__class__ == Category:
return False
return self.name == other.name and self.argstup() == other.argstup()
def subset_of(self, other):
return self.intersect(other) == self
def argstup(self):
"""
Returns arguments as a sorted tuple.
"""
retval = []
keys = self.args.keys()
keys.sort()
for key in keys:
if self.args[key].full(): continue
retval.append((key, self.args[key]))
return tuple(retval)
def fill(self, info):
"""
Set any arguments matched by this category that don't have explicitly
required values to require the values in `info`.
"""
args = {}
for key, value in self.args.iteritems():
if not info.has_key(key):
args[key] = value
else:
args[key] = value.intersect(info[key])
return Category(self.name, **args)
|