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)
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__