Python:根据几个值合并n个字典+sum

Python: merge n-dictionaries depending on several values + sum

假设我有这样一个字典列表:

[{'amount': 42140.0, 'name': 'Payment', 'account_id_credit': 385, 'type': u'expense', 'account_id_debit': 476}, 
{'amount': 43926.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'payable', 'account_id_debit': 641}, 
{'amount': 3800.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'expense', 'account_id_debit': 476},
{'amount': 46330.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'expense', 'account_id_debit': 476}, 
{'amount': 67357.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'payable', 'account_id_debit': 323},
{'amount': 26441.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'expense', 'account_id_debit': 476} ... ]

我想将字典合并在一起,这样键 "amount" 将是字典中所有 amounts 的总和,其中 account_id_creditaccount_id_debit 相同但前提是其中 typeexpense。其他 types 应该保持原样。

最好的方法是什么?

一种方法是创建一个中间字典,以 (account_id_credit, account_id_debit) 的元组为键,总金额值 运行,然后从中构建聚合字典列表:

ld = [{'amount': 42140.0, 'name': 'Payment', 'account_id_credit': 385, 'type': u'expense', 'account_id_debit': 476}, 
{'amount': 43926.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'payable', 'account_id_debit': 641}, 
{'amount': 3800.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'expense', 'account_id_debit': 476},
{'amount': 46330.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'expense', 'account_id_debit': 476}, 
{'amount': 67357.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'payable', 'account_id_debit': 323},
{'amount': 26441.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'expense', 'account_id_debit': 476} ]


d2 = {}
for d in ld:
    if d['type'] != 'expense':
        continue
    k = (d['account_id_credit'], d['account_id_debit'])
    try:
        d2[k] += d['amount']
    except KeyError:
        d2[k] = d['amount']

ld2 = []
for d in ld:
    if d['type'] != 'expense':
        ld2.append(d)
        continue
    k = (d['account_id_credit'], d['account_id_debit'])
    try:
        d['amount'] = d2[k]
        # We're done with this amount sum: remove it from the intermediate dict
        del d2[k]
    except KeyError:
        continue
    ld2.append(d)
print ld2

[{'account_id_credit': 385, 'account_id_debit': 476, 'amount': 42140.0, 'type': u'expense', 'name': 'Payment'},
 {'account_id_credit': 695, 'account_id_debit': 641, 'amount': 43926.0, 'type': u'payable', 'name': 'Payment'},
 {'account_id_credit': 695, 'account_id_debit': 476, 'amount': 76571.0, 'type': u'expense', 'name': 'Payment'},
 {'account_id_credit': 695, 'account_id_debit': 323, 'amount': 67357.0, 'type': u'payable', 'name': 'Payment'}]

您可以通过这些键聚合字典并在需要时对 amount 变量求和。

dicts = [{'amount': 42140.0, 'name': 'Payment', 'account_id_credit': 385, 'type': u'expense', 'account_id_debit': 476}, 
         {'amount': 43926.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'payable', 'account_id_debit': 641}, 
         {'amount': 3800.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'expense', 'account_id_debit': 476},
         {'amount': 46330.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'expense', 'account_id_debit': 476}, 
         {'amount': 67357.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'payable', 'account_id_debit': 323},
         {'amount': 26441.0, 'name': 'Payment', 'account_id_credit': 695, 'type': u'expense', 'account_id_debit': 476}]


def aggregate(dicts, keys):
    def worker(aggr, dic):
        key_vals = tuple(dic[key] for key in keys)
        aggr.setdefault(key_vals, {key: [] for key in dic.iterkeys()})
        for key, value in dic.iteritems():
            aggr[key_vals][key].append(value)
        return aggr

    assert len(set(tuple(dic.iterkeys()) for dic in dicts)) == 1
    return reduce(worker, dicts, {})


keys = ("account_id_credit", "type", "account_id_debit")
aggr_expense = [dic for keys, dic in aggregate(dicts, keys).iteritems() if keys[1] == u"expense"]
merged_expense = [{key: sum(value) if key == "amount" else value[0] for key, value in dic.iteritems()}
                  for dic in aggr_expense]
result = merged_expense + filter(lambda dic: dic["type"] != u"expense", dicts)
print(result)

输出:

[{'account_id_credit': 695, 'account_id_debit': 476, 'amount': 76571.0, 'type': u'expense', 'name': 'Payment'},
 {'account_id_credit': 385, 'account_id_debit': 476, 'amount': 42140.0, 'type': u'expense', 'name': 'Payment'},
 {'account_id_credit': 695, 'account_id_debit': 641, 'amount': 43926.0, 'type': u'payable', 'name': 'Payment'}, 
 {'account_id_credit': 695, 'account_id_debit': 323, 'amount': 67357.0, 'type': u'payable', 'name': 'Payment'}]