5

Python collection counter Curious if there is a better way to do this. Overriding a Counter class method? The built-in multiply produces the dot product of two counters

from collections import Counter
a = Counter({'b': 4, 'c': 2, 'a': 1})
b = Counter({'b': 8, 'c': 4, 'a': 2})    
newcounter = Counter()
for x in a.elements():
    for y in b.elements():
        if x == y:
             newcounter[x] = a[x]*b[y]

$ newcounter
Counter({'b': 32, 'c': 8, 'a': 2})
smci
  • 32,567
  • 20
  • 113
  • 146
codervince
  • 316
  • 4
  • 15
  • Beware that iterating over `.elements()` gives elements in arbitrary order. Unless `a.elements()` happens to be identical to `b.elements()`, the result will be meaningless. You probably want to iterate over `sorted(a.keys())` instead, then calculate `a[key]*b[key]` – smci Jul 12 '18 at 00:06
  • Suggest you change your example to `b = Counter({'c': 4, 'a': 2, 'b':8})` to stress that. – smci Jul 12 '18 at 00:09

3 Answers3

6

Assuming a and b always have the same keys, you can achieve this with a dictionary comprehension as follows:

a = Counter({'b': 4, 'c': 2, 'a': 1})
b = Counter({'b': 8, 'c': 4, 'a': 2})
c = Counter({k:a[k]*b[k] for k in a})
print(c)

Output

Counter({'b': 32, 'c': 8, 'a': 2})
gtlambert
  • 11,711
  • 2
  • 30
  • 48
3

You can get the intersection of the keys if you don't have identical dicts:

from collections import Counter

a = Counter({'b': 4, 'c': 2, 'a': 1, "d":4})
b = Counter({'b': 8, 'c': 4, 'a': 2})

# just .keys() for python3
print Counter(({k: a[k] * b[k] for k in a.viewkeys() & b}))
Counter({'b': 32, 'c': 8, 'a': 2})

Or if you want to join both you can or the dicts and use dict.get:

from collections import Counter

a = Counter({'b': 4, 'c': 2, 'a': 1, "d":4})
b = Counter({'b': 8, 'c': 4, 'a': 2})


print Counter({k: a.get(k,1) * b.get(k, 1) for k in a.viewkeys() | b})
Counter({'b': 32, 'c': 8, 'd': 4, 'a': 2})

If you wanted to be able to use the * operator on the Counter dicts you would have to roll your own:

class _Counter(Counter):
    def __mul__(self, other):
        return _Counter({k: self[k] * other[k] for k in self.viewkeys() & other})

a = _Counter({'b': 4, 'c': 2, 'a': 1, "d": 4})
b = _Counter({'b': 8, 'c': 4, 'a': 2})

print(a * b)

Which would give you:

_Counter({'b': 32, 'c': 8, 'a': 2})

If you wanted inplace:

from collections import Counter


class _Counter(Counter):
    def __imul__(self, other):
        return _Counter({k: self[k] * other[k] for k in self.viewkeys() & other})

Output:

In [28]: a = _Counter({'b': 4, 'c': 2, 'a': 1, "d": 4})

In [29]: b = _Counter({'b': 8, 'c': 4, 'a': 2})

In [30]: a *= b

In [31]: a
Out[31]: _Counter({'a': 2, 'b': 32, 'c': 8})
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
  • that self.viewkeys() was (probably) what I was after. Thanks. My first version of overriding the __mul__ method did not change the object. – codervince Mar 01 '16 at 11:11
  • 1
    No worries, there are multiple set operations you can use on dict view objects https://docs.python.org/2/library/stdtypes.html#dictionary-view-objects – Padraic Cunningham Mar 01 '16 at 11:23
  • 1
    Wherever there is `collections` or `itertools` there you'll find @PadraicCunningham .. :) – Iron Fist Mar 01 '16 at 15:02
1

This seems a bit better:

a = Counter({'b': 4, 'c': 2, 'a': 1})
b = Counter({'b': 8, 'c': 4, 'a': 2})    

newcounter = Counter({k:a[k]*v for k,v in b.items()})

>>> newcounter
Counter({'b': 32, 'c': 8, 'a': 2})
mhawke
  • 84,695
  • 9
  • 117
  • 138