summaryrefslogtreecommitdiffstats
path: root/category.py
blob: 1bccda1579029f1ff1b76c742cc07039938e8e6f (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
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)