Update StreamDict to include argon
[integration/test.git] / csit / libraries / Counter.py
1 from operator import itemgetter
2 from heapq import nlargest
3 from itertools import repeat, ifilter
4
5
6 class Counter(dict):
7     """Dict subclass for counting hashable objects.  Sometimes called a bag
8     or multiset.  Elements are stored as dictionary keys and their counts
9     are stored as dictionary values.
10
11     >>> Counter('zyzygy')
12     Counter({'y': 3, 'z': 2, 'g': 1})
13
14     """
15
16     def __init__(self, iterable=None, **kwds):
17         """Create a new, empty Counter object.  And if given, count elements
18         from an input iterable.  Or, initialize the count from another mapping
19         of elements to their counts.
20
21         >>> c = Counter()                           # a new, empty counter
22         >>> c = Counter('gallahad')                 # a new counter from an iterable
23         >>> c = Counter({'a': 4, 'b': 2})           # a new counter from a mapping
24         >>> c = Counter(a=4, b=2)                   # a new counter from keyword args
25
26         """
27         self.update(iterable, **kwds)
28
29     def __missing__(self, key):
30         return 0
31
32     def most_common(self, n=None):
33         """List the n most common elements and their counts from the most
34         common to the least.  If n is None, then list all element counts.
35
36         >>> Counter('abracadabra').most_common(3)
37         [('a', 5), ('r', 2), ('b', 2)]
38
39         """
40         if n is None:
41             return sorted(self.iteritems(), key=itemgetter(1), reverse=True)
42         return nlargest(n, self.iteritems(), key=itemgetter(1))
43
44     def elements(self):
45         """Iterator over elements repeating each as many times as its count.
46
47         >>> c = Counter('ABCABC')
48         >>> sorted(c.elements())
49         ['A', 'A', 'B', 'B', 'C', 'C']
50
51         If an element's count has been set to zero or is a negative number,
52         elements() will ignore it.
53
54         """
55         for elem, count in self.iteritems():
56             for _ in repeat(None, count):
57                 yield elem
58
59     # Override dict methods where the meaning changes for Counter objects.
60
61     @classmethod
62     def fromkeys(cls, iterable, v=None):
63         raise NotImplementedError(
64             "Counter.fromkeys() is undefined.  Use Counter(iterable) instead."
65         )
66
67     def update(self, iterable=None, **kwds):
68         """Like dict.update() but add counts instead of replacing them.
69
70         Source can be an iterable, a dictionary, or another Counter instance.
71
72         >>> c = Counter('which')
73         >>> c.update('witch')           # add elements from another iterable
74         >>> d = Counter('watch')
75         >>> c.update(d)                 # add elements from another counter
76         >>> c['h']                      # four 'h' in which, witch, and watch
77         4
78
79         """
80         if iterable is not None:
81             if hasattr(iterable, "iteritems"):
82                 if self:
83                     self_get = self.get
84                     for elem, count in iterable.iteritems():
85                         self[elem] = self_get(elem, 0) + count
86                 else:
87                     dict.update(self, iterable)  # fast path when counter is empty
88             else:
89                 self_get = self.get
90                 for elem in iterable:
91                     self[elem] = self_get(elem, 0) + 1
92         if kwds:
93             self.update(kwds)
94
95     def copy(self):
96         "Like dict.copy() but returns a Counter instance instead of a dict."
97         return Counter(self)
98
99     def __delitem__(self, elem):
100         "Like dict.__delitem__() but does not raise KeyError for missing values."
101         if elem in self:
102             dict.__delitem__(self, elem)
103
104     def __repr__(self):
105         if not self:
106             return "%s()" % self.__class__.__name__
107         items = ", ".join(map("%r: %r".__mod__, self.most_common()))
108         return "%s({%s})" % (self.__class__.__name__, items)
109
110     # Multiset-style mathematical operations discussed in:
111     #       Knuth TAOCP Volume II section 4.6.3 exercise 19
112     #       and at http://en.wikipedia.org/wiki/Multiset
113     #
114     # Outputs guaranteed to only include positive counts.
115     #
116     # To strip negative and zero counts, add-in an empty counter:
117     #       c += Counter()
118
119     def __add__(self, other):
120         """Add counts from two counters.
121
122         >>> Counter('abbb') + Counter('bcc')
123         Counter({'b': 4, 'c': 2, 'a': 1})
124
125
126         """
127         if not isinstance(other, Counter):
128             return NotImplemented
129         result = Counter()
130         for elem in set(self) | set(other):
131             newcount = self[elem] + other[elem]
132             if newcount > 0:
133                 result[elem] = newcount
134         return result
135
136     def __sub__(self, other):
137         """Subtract count, but keep only results with positive counts.
138
139         >>> Counter('abbbc') - Counter('bccd')
140         Counter({'b': 2, 'a': 1})
141
142         """
143         if not isinstance(other, Counter):
144             return NotImplemented
145         result = Counter()
146         for elem in set(self) | set(other):
147             newcount = self[elem] - other[elem]
148             if newcount > 0:
149                 result[elem] = newcount
150         return result
151
152     def __or__(self, other):
153         """Union is the maximum of value in either of the input counters.
154
155         >>> Counter('abbb') | Counter('bcc')
156         Counter({'b': 3, 'c': 2, 'a': 1})
157
158         """
159         if not isinstance(other, Counter):
160             return NotImplemented
161         _max = max
162         result = Counter()
163         for elem in set(self) | set(other):
164             newcount = _max(self[elem], other[elem])
165             if newcount > 0:
166                 result[elem] = newcount
167         return result
168
169     def __and__(self, other):
170         """Intersection is the minimum of corresponding counts.
171
172         >>> Counter('abbb') & Counter('bcc')
173         Counter({'b': 1})
174
175         """
176         if not isinstance(other, Counter):
177             return NotImplemented
178         _min = min
179         result = Counter()
180         if len(self) < len(other):
181             self, other = other, self
182         for elem in ifilter(self.__contains__, other):
183             newcount = _min(self[elem], other[elem])
184             if newcount > 0:
185                 result[elem] = newcount
186         return result
187
188
189 if __name__ == "__main__":
190     import doctest
191
192     print(doctest.testmod())