Can I reduce and sum a list straight into a dict?

Viewed 68

I have a list with individual dicts, like so:

dct = [{ 'winner': 'Person A' }, { 'winner': 'Person B' }, { 'winner': 'Person B' } ...]

(the dicts have other properties, they're just not relevant).

I want to know how many times the winner was a particular string. I could do something like this:

aWins = sum((1 if d['winner'] == 'Person A' else 0) for d in dct)

then do the same for 'Person B', and maybe possibly 'Person C' if that ever comes up, etc.

However, that doesn't strike me as particularly neat or pythonic. I'm still learning all the pythonic ways of doing things (I daily drive JS, so usually I'd be doing this via reduce), so is it possible to do this more neatly? Ideally, the output is a dict that looks something like this:

{ 'Person A': 40, 'Person B': 60 }
7 Answers

You can use operator.itemgetter as a mapper to extract values of the winner key of each dict. Use collections.Counter to count the number of occurrences of each value:

from collections import Counter
from operator import itemgetter

d = [{'winner': 'A'}, {'winner': 'B'}, {'winner': 'B'}]
print(dict(Counter(map(itemgetter('winner'), d))))

This outputs:

{'A': 1, 'B': 2}

More efficient:

lst = [x['winner'] for x in dct]

print({k:lst.count(k) for k in lst})
# print({k:lst.count(k) for k in set(lst)}) # Alternatively, time-efficient but needs key sorting

Output:

{'A': 1, 'B': 2}

Highly inefficient, yet a one-liner:

dct = [{ 'winner': 'A' }, { 'winner': 'B' }, { 'winner': 'B' }]

print({k:[x['winner'] for x in dct].count(k) for k in [x['winner'] for x in dct]})

Output:

{'A': 1, 'B': 2}

You can use pandas:

import pandas as pd
dictionary = [{ 'winner': 'Person A' }, { 'winner': 'Person B' }, { 'winner': 'Person B' }]
dataframe = pd.DataFrame.from_dict(dictionary)
number = dataframe["winner"].value_counts()

Function value_counts counts occurrences of a value (in this case, a string).

Output:

Person B    2
Person A    1
Name: winner, dtype: int64

Using pandas:

import pandas as pd

my_dict = [
    { 'winner': 'A' },
    { 'winner': 'B' },
    { 'winner': 'B' },
    { 'winner': 'B' }
]

pd.DataFrame(data=my_dict) \
    .groupby("winner") \
    .agg(counts = ("winner", "count")) \
    .to_dict()["counts"]
# {'A': 1, 'B': 3}

The way I would do it is to loop once over the entire list:

from collections import defaultdict

num_wins = defaultdict(int)

for d in dicts:
    num_wins[d['winner']] += 1

Just for fun, a O(nk) one-liner, where k is the number of possible persons:

persons = list("ABC")

num_wins = {
    sum((1 if d['winner'] == person else 0) for d in dict)
    for person in persons
}

you can iterate over the list, and see if the winner present in the iterator dict object , if yes then save that value in resultant dictionary and make count over there.

dict_ = [{ 'winner': 'Person A' }, { 'winner': 'Person B' }, { 'winner': 'Person B' }]
 
from collections import defaultdict as dd
result = dd(int)
 
for dic in dict_:
    if 'winner' in dic:
        result[dic['winner']]+=1
 
print(result)
# defaultdict(<class 'int'>, {'Person A': 1, 'Person B': 2})

How about this?

dct = [{ 'winner': 'Person A' }, { 'winner': 'Person B' }, { 'winner': 'Person B' }]

cnt_a = 0
cnt_b = 0
for i in dct:
    if str(i).count('A') >0:
        cnt_a += 1
    elif str(i).count('B') >0:
        cnt_b += 1
        
result = {'Person A' : cnt_a, 'Person B' : cnt_b }
result

Output:

{'Person A': 1, 'Person B': 2}
Related