#!/usr/bin/env python3 from itertools import chain, combinations from typing import Generator balances = { # "A": -20, # "B": 10, # "C": 3, # "D": -43, # should be possible with 3 transactions (A, B, C balance excactly) "A": 50, "B": -30, "C": -20, "D": -40, } balances["E"] = -sum(balances.values()) def find_zerosum_subgroups( balances: dict[str, int], ) -> Generator[tuple[str, ...], None, None]: for n in range(2, len(balances)): for combination in combinations(balances, n): if sum(balances[key] for key in combination) == 0: yield combination def solve_greedily(balances: dict[str, int]) -> dict[tuple[str, str], int]: creditors = {} debitors = {} for k, v in balances.items(): if v > 0: creditors[k] = v else: debitors[k] = v txn = {} while not all(value == 0 for value in chain(creditors.values(), debitors.values())): for debitor, debit_value in sorted(debitors.items(), key=lambda x: x[1]): for creditor, credit_value in sorted( creditors.items(), key=lambda x: x[1], reverse=True ): sum_value = credit_value + debit_value if abs(debit_value) <= credit_value: del debitors[debitor] creditors[creditor] = sum_value txn[debitor, creditor] = abs(debit_value) else: debitors[debitor] = sum_value del creditors[creditor] txn[debitor, creditor] = credit_value break return txn def solve(balances: dict[str, int]) -> dict[tuple[str, str], int]: possibilities = [] for subgroup in find_zerosum_subgroups(balances): txn_sub = solve({k: balances[k] for k in subgroup}) txn_other = solve({k: balances[k] for k in balances if not k in subgroup}) possibilities.append(txn_sub | txn_other) if not possibilities: possibilities.append(solve_greedily(balances)) return min(possibilities, key=lambda x: len(x)) print(solve(balances))