37

If I have got something like this:

D = {'a': 97, 'c': 0 , 'b':0,'e': 94, 'r': 97 , 'g':0}

If I want for example to count the number of occurrences for the "0" as a value without having to iterate the whole list, is that even possible and how?

Mazdak
  • 105,000
  • 18
  • 159
  • 188
RowanX
  • 1,272
  • 2
  • 14
  • 27

5 Answers5

53

As mentioned in THIS ANSWER using operator.countOf() is the way to go but you can also use a generator within sum() function as following:

sum(value == 0 for value in D.values())
# Or the following which is more optimized 
sum(1 for v in D.values() if v == 0)

Or as a slightly more optimized and functional approach you can use map function by passing the __eq__ method of the integer as the constructor function.

sum(map((0).__eq__, D.values()))

Benchmark:

In [15]: D = dict(zip(range(1000), range(1000)))

In [16]: %timeit sum(map((0).__eq__, D.values()))
49.6 µs ± 770 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [17]: %timeit sum(v==0 for v in D.values())
60.9 µs ± 669 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [18]: %timeit sum(1 for v in D.values() if v == 0)
30.2 µs ± 515 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [19]: %timeit countOf(D.values(), 0)
16.8 µs ± 74.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Note that although using map function in this case may be more optimized, but in order to have a more comprehensive and general idea about the two approaches you should run the benchmark for relatively large datasets as well. Then, you can use the most proper approach based on the structure and amount of data you have.

Mazdak
  • 105,000
  • 18
  • 159
  • 188
  • Probably better because generators tend to be a bit slower. – juanpa.arrivillaga Jan 21 '18 at 21:42
  • In fact, I'm getting `%timeit sum([value == 0 for value in D.values()])` as faster than the generator expression version. – juanpa.arrivillaga Jan 21 '18 at 21:43
  • @juanpa.arrivillaga Definitely but the interesting part is that `(0).__eq__` is not a built-in function and yet it performs better with map. That means the drawback of generators (extra function calls, `__next__`, etc) is more influential than passing a non-built-in function to `map`. – Mazdak Jan 21 '18 at 21:46
  • Also, the creation of the actual generator object might have more overhead than the creation of the `map` object, and with a `dict` this small, it would make a difference – juanpa.arrivillaga Jan 21 '18 at 21:48
  • 1
    `sum(1 for value in D.values() if value == 0)` is likely faster. But I'd say `countOf` is really the best way. – Kelly Bundy Dec 15 '21 at 09:15
  • @KellyBundy Yup, seems like some new under the hood optimizations. The `countOf` is also the best way tho. – Mazdak Dec 15 '21 at 14:47
  • With under the hood optimizations, are you talking about the `sum(1 ... if)`? I think that mostly benefits from fewer values transferred to and processed by `sum`, same reason as [this](https://stackoverflow.com/questions/68938628/why-is-any-true-for-if-cond-much-faster-than-any-cond-for). Especially when the value appears very rarely, like in your test data. – Kelly Bundy Dec 15 '21 at 21:34
27

Alternatively, using collections.Counter:

from collections import Counter
D = {'a': 97, 'c': 0 , 'b':0,'e': 94, 'r': 97 , 'g':0}

Counter(D.values())[0]
# 3
Nick K9
  • 3,885
  • 1
  • 29
  • 62
blacksite
  • 12,086
  • 10
  • 64
  • 109
12

You can count it converting it to a list as follows:

D = {'a': 97, 'c': 0 , 'b':0,'e': 94, 'r': 97 , 'g':0}
print(list(D.values()).count(0))
>>3

Or iterating over the values:

print(sum([1 for i in D.values() if i == 0]))
>>3
user1767754
  • 23,311
  • 18
  • 141
  • 164
4

That's a job for operator.countOf.

countOf(D.values(), 0)

Benchmark with your example dictionary:

1537 ns  1540 ns  1542 ns  Counter(D.values())[0]
 791 ns   800 ns   802 ns  sum(value == 0 for value in D.values())
 694 ns   697 ns   717 ns  sum(map((0).__eq__, D.values()))
 680 ns   682 ns   689 ns  sum(1 for value in D.values() if value == 0)
 599 ns   599 ns   600 ns  sum([1 for i in D.values() if i == 0])
 368 ns   369 ns   375 ns  list(D.values()).count(0)
 229 ns   231 ns   231 ns  countOf(D.values(), 0)

Code (Try it online!):

from timeit import repeat

setup = '''
from collections import Counter
from operator import countOf
D = {'a': 97, 'c': 0 , 'b':0,'e': 94, 'r': 97 , 'g':0}
'''

E = [
    'Counter(D.values())[0]',
    'sum(value == 0 for value in D.values())',
    'sum(map((0).__eq__, D.values()))',
    'sum(1 for value in D.values() if value == 0)',
    'sum([1 for i in D.values() if i == 0])',
    'list(D.values()).count(0)',
    'countOf(D.values(), 0)',
]

for _ in range(3):
    for e in E:
        number = 10 ** 5
        ts = sorted(repeat(e, setup, number=number))[:3]
        print(*('%4d ns ' % (t / number * 1e9) for t in ts), e)
    print()
Kelly Bundy
  • 23,480
  • 7
  • 29
  • 65
1
for i in hashmap:    
  print(Counter(hashmap.values())[hashmap[i]])

# In this way we can traverse & check the count with the help of Counter 
  • This is already provided in this [answer](https://stackoverflow.com/a/48371896/7758804), so I can only assume this is posted as a "thank you" or "confirmation" comment. – Trenton McKinney Jun 28 '22 at 19:48