Home > Software design >  How to count occurrences of specific type in a list?
How to count occurrences of specific type in a list?

Time:10-20

Is there an easy way to count how many elements of a specific type there are in a list?

This is what I have in mind and what I tried.

ex_list = ["string", "data", "item", 1, 3, {3: "im dict"}, "im_item"]

print(ex_list.count(int)) # -> 0, should return 2
print(ex_list.count(type(str))) # -> 0, should return 4

I know that there are workarounds (using loops, etc.) but I was wondering if there is anything as simple as using just one function (like count function, etc.).

CodePudding user response:

from operator import countOf

countOf(map(type, ex_list), int)

Try it online!

docs

Benchmark:

 644 ns   646 ns   671 ns  countOf(map(type, ex_list), int)
2102 ns  2149 ns  2154 ns  Counter(map(type, ex_list))[int]
1125 ns  1150 ns  1183 ns  sum(type(x) is int for x in ex_list)

Benchmark with a 1000 times longer list:

 392 μs   396 μs   397 μs  countOf(map(type, ex_list), int)
 684 μs   696 μs   699 μs  Counter(map(type, ex_list))[int]
 854 μs   887 μs   896 μs  sum(type(x) is int for x in ex_list)

So for a "specific type" as your question asks, it's fastest of those methods, but Counter might be faster if you want multiple types (because once it's created, looking up a type in the Counter only takes O(1) time).

Benchmark code (Try it online!):

from timeit import repeat

setup = '''
from operator import countOf
from collections import Counter
ex_list = ["string", "data", "item", 1, 3, {3: "im dict"}, "im_item"]
'''

E = [
    'countOf(map(type, ex_list), int)',
    'Counter(map(type, ex_list))[int]',
    'sum(type(x) is int for x in ex_list)',
]

print('example list:')
for _ in range(3):
    for e in E:
        number = 100000
        times = sorted(repeat(e, setup, number=number))[:3]
        print(*('M ns ' % (t / number * 1e9) for t in times), e)
    print()

print('1000 times longer list:')
setup  = 'ex_list *= 1000'
for _ in range(3):
    for e in E:
        number = 100
        times = sorted(repeat(e, setup, number=number))[:3]
        print(*('M μs ' % (t / number * 1e6) for t in times), e)
    print()

CodePudding user response:

You could use the Counter class (from collections) with a mapping to types:

from collections import Counter

ex_list = ["string", "data", "item", 1, 3, {3: "im dict"}, "im_item"]

typeCounts = Counter(map(type,ex_list))
>>> typeCounts
Counter({<class 'str'>: 4, <class 'int'>: 2, <class 'dict'>: 1})
>>> typeCounts[int]
2

This will do the initial counting in O(n) time and every use of typeCounts[type] will be O(1) thereafter.

On the other hand, if you are only looking once for a specific type, you could do it with sum():

sum(isinstance(e,int) for e in ex_list) # 4

CodePudding user response:

I wouldn't call a loop a workaround; any solution to this is going to use a loop, whether it's written in Python or under the hood. So, here's a loop solution using isinstance(), which allows subclasses.

def type_count(iterable, type_):
    return sum(isinstance(x, type_) for x in iterable)
>>> type_count(ex_list, int)
2
>>> type_count(ex_list, str)
4

If you wanted to use the Counter solution but allow subclasses, you could go through each type's method resolution order (MRO), like this:

from collections import Counter

typeCounts = Counter(map(type, ex_list))
for t, n in list(typeCounts.items()):  # list() since the dict will change size
    for parent in t.__mro__[1:]:
        typeCounts[parent]  = n
>>> typeCounts[int]
2
>>> typeCounts[object] == len(ex_list)
True

docs: class.__mro__

  • Related