53

A set uses .update to add multiple items, and .add to add a single one.

Why doesn't collections.Counter work the same way?

To increment a single Counter item using Counter.update, it seems like you have to add it to a list:

from collections import Counter

c = Counter()
for item in something:
    for property in properties_of_interest:
        if item.has_some_property: # simplified: more complex logic here
            c.update([item.property])
        elif item.has_some_other_property:
            c.update([item.other_property])
        # elif... etc

Can I get Counter to act like set (i.e. eliminate having to put the property in a list)?

Use case: Counter is very nice because of its defaultdict-like behavior of providing a default zero for missing keys when checking later:

>>> c = Counter()
>>> c['i']
0

I find myself doing this a lot as I'm working out the logic for various has_some_property checks (especially in a notebook). Because of the messiness of that, a list comprehension isn't always desirable etc.

scharfmn
  • 3,561
  • 7
  • 38
  • 53
  • So whats wrong with your code and `Counter.update`? – Mazdak May 02 '15 at 11:44
  • 1
    I have to stick it onto a fake list. It's not a big deal; I'm just wondering if there's a thought behind no `add`. – scharfmn May 02 '15 at 11:46
  • The `update` method accept an iterable aas argument and the iterable is expected to be a sequence of elements, not a sequence of (key, value) pairs. – Mazdak May 02 '15 at 11:49
  • 2
    Like I said in a comment - how does `add` even help you there - you're doing selections on **what** to add... I'm failing to see the point - can you give an example of how the `.add` would work? – Jon Clements May 02 '15 at 12:01
  • 1
    Does `has_some_property` actually have different logic than just taking the property itself? - Also note - with the current `helper` you may get `None` results into your `Counter` - which may be desirable or not... – Jon Clements May 02 '15 at 12:07
  • It's a simple example. I have had cases with complex logic, but the thing that was really bugging me was no `add`... Re: `helper`: great point. I can imagine `Counter(helper(item) for item in something if helper(item))` but that's two calls. – scharfmn May 02 '15 at 12:14

3 Answers3

47

Well, you don't really need to use methods of Counter in order to count, do you? There's a += operator for that, which also works in conjunction with Counter.

c = Counter()
for item in something:
    if item.has_some_property:
        c[item.property] += 1
    elif item.has_some_other_property:
        c[item.other_property] += 1
    elif item.has_some.third_property:
        c[item.third_property] += 1
shx2
  • 61,779
  • 13
  • 130
  • 153
  • 14
    What wasn't obvious to me at first is that this works correctly even if the key you are incrementing doesn't exist in the counter yet. – pallgeuer Jul 14 '20 at 10:42
  • @pallgeuer - Yeah, at this point we're effectively just using a `defaultdict(int)`... which is all `Counter()` is, with a few operators for combining them added in. – ArtOfWarfare Aug 14 '23 at 17:24
27
>>> c = collections.Counter(a=23, b=-9)

You can add a new element and set its value like this:

>>> c['d'] = 8
>>> c
Counter({'a': 23, 'd': 8, 'b': -9})

Increment:

>>> c['d'] += 1
>>> c
Counter({'a': 23, 'd': 9, 'b': -9} 

Note though that c['b'] = 0 does not delete:

>>> c['b'] = 0
>>> c
Counter({'a': 23, 'd': 9, 'b': 0})

To delete use del:

>>> del c['b']
>>> c
Counter({'a': 23, 'd': 9})

Counter is a dict subclass

Vidhya G
  • 2,250
  • 1
  • 25
  • 28
6

There is a more Pythonic way to do what you want:

c = Counter(item.property for item in something if item.has_some_property)

It uses a generator expression instead of open-coding the loop.

Edit: Missed your no-list-comprehensions paragraph. I still think this is the way to actually use Counter in practice. If you have too much code to put into a generator expression or list comprehension, it is often better to factor that into a function and call that from a comprehension.

Christian Aichinger
  • 6,989
  • 4
  • 40
  • 60
  • A list comprehension, a generator expression -- can do it, but with complex if/elif/elif/etc, it doesn't work. – scharfmn May 02 '15 at 11:50
  • @bahmait and how do you imagine `set.add` helps with complex `if/elif/elif` etc...? – Jon Clements May 02 '15 at 11:56
  • @bahmait: Just edited. Factoring that logic into a separate function is the Pythonic way to do this, I guess. I agree that an ``add()`` method might be a nice addition, but perhaps the omission was deliberate to push people to the generator style. – Christian Aichinger May 02 '15 at 11:56
  • @JonClements wouldn't it make things easy to read if doing a bunch of things on each pass in addition to updating the counter? I was looking for the `+= 1` for cases where slow is fine, and having all the logic in one place is nice. Does that make sense? – scharfmn May 02 '15 at 12:20
  • @bahmait yup... the answer you've accepted is definitely the most readable and scalable :) – Jon Clements May 02 '15 at 12:25
  • @JonClements have edited the question in response to your comments -- appreciate your help. – scharfmn May 02 '15 at 12:58