Home > Software engineering >  How to sum and count values within a list of dicts?
How to sum and count values within a list of dicts?

Time:11-17

I have a list of Dicts as follows

[{"Sender":"bob","Receiver":"alice","Amount":50},{"Sender":"bob","Receiver":"alice","Amount":60},{"Sender":"bob","Receiver":"alice","Amount":70},{"Sender":"joe","Receiver":"bob","Amount":50},{"Sender":"joe","Receiver":"bob","Amount":150},{"Sender":"alice","Receiver":"bob","Amount":100},{"Sender":"bob","Receiver":"kyle","Amount":260}]

What i need is to sum up the totals per each unique sender/receiver pair, as well as how many total "transactions" there were per pair, as shown below in my desired output

[{"Sender":"bob","Receiver":"alice","Total":180,"Count":3},{"Sender":"joe","Receiver":"bob","Total":"200","Count":2},{"Sender":"alice","Receiver":"bob","Total":"100","Count":1}, {"Sender":"bob","Receiver":"kyle","Total":260,"Count":1}]

What i'm currently doing to get the "total" is

total = sum(a['Amount'] for a in transactions).

But this simply sums up all of the amounts across all pairs, i need the total for each unique pair of sender/receiver i would't know where to begin getting the "count" numbers, either.

CodePudding user response:

Make a new dictionary where the key is the sender/receiver pair.

Iterate over the list of senders/receivers. If that sender/receiver pair does not exist in the new dict, create it. Otherwise increment the count for that pair by one.

newdict = {}
for row in transactions:
    sender = row['Sender']
    receiver = row['Receiver']
    amount = row['Amount']

    key = f'{sender},{receiver}'
    if key in newdict:
        newdict[key]['Total']  = amount
        newdict[key]['Count']  = 1
    else:
        newdict[key] = {'Count': 1, 'Total': amount}

This solution produces a single dict instead of a list of dicts.

CodePudding user response:

lookup for existing value and keep updating it. e.g.

transactions = [
    {"Sender":"bob","Receiver":"alice","Amount":50},
    {"Sender":"bob","Receiver":"alice","Amount":60},
    {"Sender":"bob","Receiver":"alice","Amount":70},
    {"Sender":"joe","Receiver":"bob","Amount":50},
    {"Sender":"joe","Receiver":"bob","Amount":150},
    {"Sender":"alice","Receiver":"bob","Amount":100},
    {"Sender":"bob","Receiver":"kyle","Amount":260}
]

output = []

def find_transaction(sender: str, receiver: str):
    for o in output:
        if o["Sender"] == sender and o["Receiver"] == receiver:
            return o
    return None

for tx in transactions:
    existing = find_transaction(tx["Sender"], tx["Receiver"])
    
    if existing:
        existing["Total"]  = tx["Amount"]
        existing["Count"]  = 1
    else:
        output.append({
            "Sender": tx["Sender"],
            "Receiver": tx["Receiver"],
            "Total": tx["Amount"],
            "Count": 1
        })

print(output)

CodePudding user response:

  • Use a dict to group the amounts, using (sender, receiver) as key;
  • Rebuild your list of dicts from the dict of amounts.
ll = [{"Sender":"bob","Receiver":"alice","Amount":50},{"Sender":"bob","Receiver":"alice","Amount":60},{"Sender":"bob","Receiver":"alice","Amount":70},{"Sender":"joe","Receiver":"bob","Amount":50},{"Sender":"joe","Receiver":"bob","Amount":150},{"Sender":"alice","Receiver":"bob","Amount":100},{"Sender":"bob","Receiver":"kyle","Amount":260}]

# GROUP BY (SENDER, RECEIVER) AND REDUCE BY KEY

d = {}
for transaction in ll:
    k = (transaction['Sender'], transaction['Receiver'])
    d[k] = d.get(k, 0)   transaction['Amount']

# print(d)
# d = {('bob', 'alice'): 180, ('joe', 'bob'): 200, ('alice', 'bob'): 100, ('bob', 'kyle'): 260}

# REBUILD LIST OF DICT

new_ll = [{'Sender': s, 'Receiver': r, 'Amount': a} for (s,r),a in d.items()]

print(new_ll)
# [{'Sender': 'bob', 'Receiver': 'alice', 'Amount': 180}, {'Sender': 'joe', 'Receiver': 'bob', 'Amount': 200}, {'Sender': 'alice', 'Receiver': 'bob', 'Amount': 100}, {'Sender': 'bob', 'Receiver': 'kyle', 'Amount': 260}]

These kinds of group-by-key and reduce-by-key operations are extremely common. Using a dict to group by key is the best method. There is also a library function in module more_itertools:

from more_itertools import map_reduce
from operator import itemgetter

ll = ll = [{"Sender":"bob","Receiver":"alice","Amount":50},{"Sender":"bob","Receiver":"alice","Amount":60},{"Sender":"bob","Receiver":"alice","Amount":70},{"Sender":"joe","Receiver":"bob","Amount":50},{"Sender":"joe","Receiver":"bob","Amount":150},{"Sender":"alice","Receiver":"bob","Amount":100},{"Sender":"bob","Receiver":"kyle","Amount":260}]

d = map_reduce(ll, keyfunc=itemgetter('Sender', 'Receiver'), valuefunc=itemgetter('Amount'), reducefunc=sum)

print(d)
# defaultdict(None, {('bob', 'alice'): 180, ('joe', 'bob'): 200, ('alice', 'bob'): 100, ('bob', 'kyle'): 260})

new_ll = [{'Sender': s, 'Receiver': r, 'Amount': a} for (s,r),a in d.items()]
  • Related