summaryrefslogtreecommitdiffstats
path: root/category.py
blob: ad337747b5fb17f74053ca33d85ecf0f9939f885 (plain)
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
__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(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)