algorithm to determine minimum payments amongst a group - algorithm

The Problem
I was recently asked to calculate the money owed amongst a group of people who went on a trip together and came upon an interesting problem: given that you know the amounts that each person owes another, what is a general algorithm to consolidate the debts between people so that only the minimum number of payments needs to be made? Take this as an example:
Mike owes John 100
John owes Rachel 200
Mike owes Rachel 400
We can remove a payment between Mike and John by reformulating the debts like this:
Mike owes John 0
John owes Rachel 100
Mike owes Rachel 500
I did the math by hand since it was easy enough, but then the programmer in me was itching to figure out a general algorithm to do it for an arbitrarily large group. This seems like a graph algorithm to me, so I'll reformulate this as a graph:
Viewed as a Graph
The vertices are the people in the group
The edges are directed and weighted by the amount owed. For example, an edge from Mike to Rachel with weight 500 means that Mike owes Rachel 500.
Constraint: the net sum of weights for each node must remain unchanged.
The goal is to find a graph with the minimum number of edges that still satisfy the constraint.

My opinion: You're making this overly complicated.
Think of it as a "pool" of money, and lose the relationships altogether:
Instead of:
Mike owes John 100
John owes Rachel 200
Mike owes Rachel 400
The algorithm only has to think:
Mike owes 100
John is owed 100
John owes 200
Rachel is owed 200
Mike owes 400
Rachel is owed 400
Netting this:
Mike owes 500
John owes 100
Rachel is owed 600
Separate this into a list of "givers" and "receivers". Each giver on the list will go through the list of receivers, giving each receiver what they need until the giver has payed up. When a receiver receives everything they need, they go off the list.
Later Edit
As other posters have observed, this simplifies the problem. However, there might be an optimal ordering of the "givers" and "receivers" lists, but we haven't yet identified a straightforward way to determine this ordering.

It does not suffice to just figure out the receivers and givers. While I think this strategy is on the right track it also does not ensure an algorithm to find the least possible amount of payments.
For Example,
Person A owes 25
Person B owes 50
Person C owes 75
Person D is owed 100
Person E is owed 50
While it is obvious that this can be done in 3 pays (A and C to D, B to E). I can't think of an efficient algorithm that will satisfy this for all problem sets.
Better Example,
Person A owes 10
Person B owes 49
Person C owes 50
Person D owes 65
Person E is owed 75
Person F is owed 99
If we took the greedy approach of having person D pay to F we would wind up with a sub-optimal solution as opposed to the optimal(A&D to E, B&C to F).
This problem has a lot of similarity with the Bin Packing Problem which has been proven to be NP-hard. The only difference being that we have multiple bins of varying sizes and the condition that the total space in all bins is equal to the total size of all items. This leads me to believe that the problem is likely NP-hard but with the added constraints it may be possible to solve in polynomially time.

Take a look at this blog article, "Optimal Account Balancing", goes over your problem exactly.

In the world of the corporate treasury, this is known as payment or settlement netting.
Multinational corporates usually have many flows between their subsidiaries every month, often in different currencies. They can save considerable amounts by optimising the settlement of these flows. Typically a corporate will perform such an optimisation (a netting cycle) once a month. When there are multiple currencies, there are three sources of savings:
bank transaction fees (fewer payments means lower fees)
lower interest rate float and settlement risk arising from streamlined processing by the bank (the money spends less time tied up in the banking system)
FX matching (much less foreign currency is exchanged because most of it is 'netted out')
There are two ways to actually calculate the optimised settlement.
Bilateral netting is the solution well described by #AndrewShepherd on this page. However, in a cross-border implementation, this approach can have legal and administrative problems implications since different borders are being crossed each month.
Multilateral netting solves the network by adding a new subsidiary called the netting centre and re-routes all amounts through it. Compare the before and after diagrams below:
Before netting
After netting
Although this adds one more flow than is necessary (compared to bi-lateral netting), the advantages are:
the calculation is simpler and the result is easier to visualise (also, there is only one solution, as opposed to the bilateral approach)
the netting centre becomes an invaluable resource regarding flows and FX exposure within the whole group
if the netting centre is, say, in Germany, then all legal issues with cross-border payments are dealt once and for all within the netting centre's country (central bank reporting, etc.)
all foreign exchange required for the optimised settlement can be bought or sold from the netting centre
(At it's basic level, the calculation is simple, but there can be many legal and administrative complications so corporates frequently develop or purchase a netting system from a software vendor or service provider.)

While I concur with #Andrew that turning this into a graph problem is probably overcomplicated, I'm not sure his approach yields the minimal number of transactions. It's how you'd solve the problem in real life to save yourself a headache; just pool the money.
A few steps that seem 'right':
Remove all individuals with zero debt; they don't need to send or receive money from anyone.
Pair all givers and receivers with identical amounts owed/owing. Since the minimal connectivity per node of non-zero debt is 1, their transactions are already minimal if they just pay each other. Remove them from the graph.
Starting with the individual with the largest amount to pay back, create a list of all receivers with owed less than that amount. Try all combinations of payment until one is found that satisfies most receivers with one transaction. 'Save' the surplus debt remaining.
Move on to the next largest giver, etc.
Allocate all the surplus debt to the remaining receivers.
As always, I'm afraid I'm pretty sure about the first two steps, less sure about the others. In any case, it does sound like a textbook problem; I'm sure there's a 'right' answer out there.

if A, B and C each owe $1 to each of D, E and F, the "list" or "central bank" solution creates five transactions (e.g. A,B,C -$3-> D, D -$3-> E,F) whereas the naive solution results in nine transactions. However, if A owes only to D, B only to E and C only to F the central bank solution creates still five transactions (A,B,C -$1-> D, D -$1-> E,F) whereas the best solution needs only three (A -$1-> D, B -$1-> E, C -$1-> F). This shows that the "list" or "central bank" solution is not optimal in general.
The following greedy algorithm can be used to create better solutions to the problem, but they are not always optimal. Let "debt[i,j]" denote the amount of money person i owes to person j; initially this array is initialized according to the situation.
repeat until last:
find any (i, j) such that |K = {k : debt[i,k] > 0 and debt[j,k] > 0}| >= 2
if such (i, j) found:
// transfer the loans from i to j
total = 0
for all k in K:
debt[j,k] += debt[i,k]
total += debt[i,k]
debt[i,k] = 0
person i pays 'total' to person j
else:
last
for every i, j in N:
if (debt[i,j] > 0)
person i pays debt[i,j] to person j
They key of this algorithm is the observation that if both A and B owe money to both C and D, instead of the four transactions required for direct payments, B can pay the net debt to A who can take care of paying back both A's and B's loans.
To see how this algorithm works, consider the case where A, B and C each own $1 to each of D, E, F:
A transfers A's debts to B, and pays $3 to B (one transaction)
B transfers B's debts to C, and pays $6 to C (one transaction)
Only C has debts any more; C pays $3 to D, E and F each (three transactions)
But in the case where A owes D, B owes E and C owes F, the algorithm falls through immediately to the payment loop, resulting in the optimal number of transactions (only three) instead of five transactions which would result from the "central bank" approach.
An example of non-optimality is where A owes to D and E, B owes to E and F and C owes to F and D (assume $1 for every debt). The algorithm fails to consolidate the loans because no two payers share two common payees. This could be fixed by changing ">= 2" on the second line to ">= 1", but then the algorithm would most likely become very sensitive to the order in which the debts are collateralized.

So, I implemented this for a spreadsheet to keep track of my roommates' debt to each other. I know this is really old, but I referenced it in my solution, and it's high on Google when searching for the subject, so I wanted to post and see if anyone has any input.
My solution uses the "central bank" or "netting center" concept people mentioned here. Before running this algorithm I calculate the net profit each person is owed, which is the sum of all their credits, minus the sum of all their debts. The complexity of calculating this is dependent on the number of transactions, not the number of individuals involved.
Fundamentally the point of the algorithm is to have every individual be paid or pay out the correct amount regardless of what individual they are transferring money to. I would ideally like to do this in the fewest number of payments, however it's difficult to prove this to be the case. Note all debits and credits will sum to zero.
I was very, very verbose for part of this code. In part to communicate what I was doing, and in part to solidify the logic I am using as I go forward. Apologies if it's unreadable.
Input: {(Person, Net Profit)} //Net Profit < 0 is debt, Net Profit > 0 is credit.
Output: {(Payer, Payee, Amount paid)}
find_payments(input_list):
if input_list.length() > 2:
//More than two people to resolve payments between, the non-trivial case
max_change = input_list[0]
not_max_change = []
//Find person who has the highest change to their account, and
//the list of all others involved who are not that person
for tuple in input_list:
if abs(tuple[Net Profit]) > abs(max_change[Net Profit])
not_max_change.push(max_change)
max_change = tuple
else:
not_max_change.push(tuple)
//If the highest change person is owed money, they are paid by the
//person who owes the most money, and the money they are paid is deducted
//from the money they are still owed.
//If the highest change person owes money, they pay the person who
//is owed the most money, and the money they pay is deducted
//from the money they are still owe.
not_yet_resolved = []
if max_change[Net Profit] > 0:
//The person with the highest change is OWED money
max_owing = not_max_change[0]
//Find the remaining person who OWES the most money
//Find the remaining person who has the LOWEST Net Profit
for tuple in not_max_change:
if tuple[Net Paid] < max_owing[Net Paid]:
not_yet_resolved.push(max_owing)
max_owing = tuple
else:
not_yet_resolved.push(tuple)
//The person who has the max change which is positive is paid
//by the person who owes the most, reducing the amount of money
//they are owed. Note max_owing[Net Profit] < 0.
max_change = [max_change[Person], max_change[Net Profit]+max_owing[Net Profit]]
//Max_owing[Person] has paid max_owing[Net Profit] to max_change[Person]
//max_owing = [max_owing[Person], max_owing[Net Profit]-max_owing[Net Profit]]
//max_owing = [max_owing[Person], 0]
//This person is fully paid up.
if max_change[Net Profit] != 0:
not_yet_resolved.push(max_owing)
//We have eliminated at least 1 involved individual (max_owing[Person])
//because they have paid all they owe. This truth shows us
//the recursion will eventually end.
return [[max_owing[Person], max_change[Person], max_owing[Net Profit]]].concat(find_payments(not_yet_resolved))
if max_change[Net Profit] < 0:
//The person with the highest change OWES money
//I'll be way less verbose here
max_owed = not_max_change[0]
//Find who is owed the most money
for tuple in not_max_change:
if tuple[Net Paid] > max_owed[Net Paid]:
not_yet_resolved.push(max_owed)
max_owed = tuple
else:
not_yet_resolved.push(tuple)
//max_change pays the person who is owed the most.
max_change = [max_change[Person], max_change[Net Profit]+max_owed[Net Profit]]
if max_change[Net Profit] != 0:
not_yet_resolved.push(max_owing)
//Note position of max_change[Person] moved from payee to payer
return [[max_change[Person], max_owed[Person], max_owed[Net Profit]]].concat(find_payments(not_yet_resolved))
//Recursive base case
//Two people owe each other some money, the person who owes pays
//the person who is owed. Trivial.
if input_list.length() == 2:
if input_list[0][Net Profit] > input_list[1][Net Profit]:
return [[input_list[1][Person], input_list[0][Person], input_list[0][Net Profit]]];
else
return [[input_list[0][Person], input_list[1][Person], input_list[1][Net Profit]]];
Note:
max_change = (payee, $A); max_owing = (payer, $B)
|$A|>=|$B| by nature of 'max_change'
$A > 0 => $A >= |$B| //max_change is owed money
$B < 0 by nature of 'max_owing'
$A >= -$B => $A + $B >= 0 => Payee does not go into debt
and:
max_change = (payee, $A); max_owed = (payer, $B)
|$A|>=|$B| by nature of 'max_change'
$A < 0 => -$A >= |$B| //max_change owes money
$B > 0 by nature of 'max_owed'
-$A >= $B => 0 >= $A + $B => Payee does not go into credit
also:
Sum of Payments = 0 = $A + $B + Remainder = ($A + $B) + 0 + Remainder
The sum always being 0 after someone's debt is completely settled is the basis of the recursion logic. Someone is paid/has paid, the problem gets smaller.
If this algorithm is running for n people with non-zero debts (discard people who broke even before running the algorithm) this algorithm will give at most n-1 payments to settle the debt. It's unclear if it is always an ideal payment scheme (I've yet to find a counter example). I may try to prove if the number of transactions < n-1 then a debt and a credit must be exactly equal, which this algorithm accounts for I believe.
I'm extremely interested in any errors anyone sees in this. I haven't done development in a while, never mind algorithmics, and people will be paying each other based off this. I had fun and this is an interesting, meaty question, I hope some of you are still around.

As one #Edd Barret stated, this can be solved approximately using linear programming.
I have written a blog post describing this approach, along with a tiny R package that implements it.

Related

How to create a map of settlement for a game of poker algorithmically containing the least number of transactions? [duplicate]

This is kind of more generic question, isn't language-specific. More about idea and algorithm to use.
The system is as follows:
It registers small loans between groups of friends. Alice and Bill are going to lunch, Bill's card isn't working, so Alice pays for his meal, $10.
The next day Bill and Charles meet each other on a railway station, Charles has no money for ticket, so Bill buys him one, for $5.
Later that day Alice borrows $5 from Charles and $1 from Bill to buy her friend a gift.
Now, assuming they all registered that transactions in the system, it looks like this:
Alice -> Bill $10
Bill -> Alice $1
Bill -> Charles $5
Charles -> Alice $5
So, now, only thing that needs to be done is Bill giving Alice $4 (he gave her $1 and Charles transferred his $5 to Alice already) and they're at the initial state.
If we scale that to many different people, having multiple transaction, what would be the best algorithm to get as little transactions as possible?
This actually looks like a job that the double entry accounting concept could help with.
Your transactions could be structured as bookkeeping entries thus:
Alice Bill Charles Balance
Alice -> Bill $10 10 10- 0 0
Bill -> Alice $1 9 9- 0 0
Bill -> Charles $5 9 4- 5- 0
Charles -> Alice $5 4 4- 0 0
And there you have it. At each transaction, you credit one ledger account and debit another so that the balance is always zero. At at the end, you simply work out the minimal number transactions to be applied to each account to return it to zero.
For this simple case, it's a simple $4 transfer from Bill to Alice. What you need to do is to reduce at least one account (but preferably two) to zero for every transaction added. Let's say you had the more complicated:
Alice Bill Charles Balance
Alice -> Bill $10 10 10- 0 0
Bill -> Alice $1 9 9- 0 0
Bill -> Charles $5 9 4- 5- 0
Charles -> Alice $5 4 4- 0 0
Charles -> Bill $1 4 5- 1 0
Then the transactions needed would be:
Bill -> Alice $4 0 1- 1 0
Bill -> Charles $1 0 0 0 0
Unfortunately, there are some states where this simple greedy strategy does not generate the best solution (kudos to j_random_hacker for pointing this out). One example is:
Alan Bill Chas Doug Edie Fred Bal
Bill->Alan $5 5- 5 0 0 0 0 0
Bill->Chas $20 5- 25 20- 0 0 0 0
Doug->Edie $2 5- 25 20- 2 2- 0 0
Doug->Fred $1 5- 25 20- 3 2- 1- 0
Clearly, this could be reversed in four moves (since four moves is all it took to get there) but, if you choose your first move unwisely (Edie->Bill $2), five is the minimum you'll get away with.
You can solve this particular problem with the following rules:
(1) if you can wipe out two balances, do it.
(2) otherwise if you can wipe out one balance and set yourself up to wipe out two in the next move, do it.
(3) otherwise, wipe out any one balance.
That would result in the following sequence:
(a) [1] not applicable, [2] can be achieved with Alan->Bill $5.
(b) [1] can be done with Chas->Bill $20.
(c) and (d), similar reasoning with Doug, Edie and Fred, for four total moves.
However, that works simply because of the small number of possibilities. As the number of people rises and the group inter-relations becomes more complex, you'll most likely need an exhaustive search to find the minimum number of moves required (basically the rules 1, 2 and 3 above but expanded to handle more depth).
I think that is what will be required to give you the smallest number of transactions in all circumstances. However, it may be that that's not required for the best answer (best, in this case, meaning maximum "bang per buck"). It may be that even the basic 1/2/3 rule set will give you a good-enough answer for your purposes.
Intuitively, this sounds like an NP-complete problem (it reduces to a problem very like bin packing), however the following algorithm (a modified form of bin packing) should be pretty good (no time for a proof, sorry).
Net out everyone's positions, i.e. from your example above:
Alice = $4
Bill = $-4
Charles = $0
Sort all net creditors from highest to lowest, and all debtors from lowest to highest, then match by iterating over the lists.
At some point you might need to split a person's debts to net everything out - here it is probably best to split into the biggest chunks possible (i.e. into the bins with the most remaining space first).
This will take something like O(n log n) (again, proper proof needed).
See the Partition Problem and Bin Packing for more information (the former is a very similar problem, and if you limit yourself to fixed precision transactions, then it is equivalent - proof needed of course).
I have created an Android app which solves this problem. You can input expenses during the trip, it even recommends you "who should pay next". At the end it calculates "who should send how much to whom". My algorithm calculates minimum required number of transactions and you can setup "transaction tolerance" which can reduce transactions even further (you don't care about $1 transactions) Try it out, it's called Settle Up:
https://market.android.com/details?id=cz.destil.settleup
Description of my algorithm:
I have basic algorithm which solves the problem with n-1 transactions, but it's not optimal. It works like this: From payments, I compute balance for each member. Balance is what he paid minus what he should pay. I sort members according to balance increasingly. Then I always take the poorest and richest and transaction is made. At least one of them ends up with zero balance and is excluded from further calculations. With this, number of transactions cannot be worse than n-1. It also minimizes amount of money in transactions. But it's not optimal, because it doesn't detect subgroups which can settle up internally.
Finding subgroups which can settle up internally is hard. I solve it by generating all combinations of members and checking if sum of balances in subgroup equals zero. I start with 2-pairs, then 3-pairs ... (n-1)pairs. Implementations of combination generators are available. When I find a subgroup, I calculate transactions in the subgroup using basic algorithm described above. For every found subgroup, one transaction is spared.
The solution is optimal, but complexity increases to O(n!). This looks terrible but the trick is there will be just small number of members in reality. I have tested it on Nexus One (1 Ghz procesor) and the results are: until 10 members: <100 ms, 15 members: 1 s, 18 members: 8 s, 20 members: 55 s. So until 18 members the execution time is fine. Workaround for >15 members can be to use just the basic algorithm (it's fast and correct, but not optimal).
Source code:
Source code is available inside a report about algorithm written in Czech. Source code is at the end and it's in English:
http://www.settleup.info/files/master-thesis-david-vavra.pdf
I found a practical solution which I implemented in Excel:
find out who ows the most
let that person pay the complete amount he ows to the one who should get the most
that makes the first person zero
repeat this proces taken into account that one of (n-1) persons has a changed amount
It should result in a maximum of (n-1) transfers and the nice thing about it is that no one is doing more than one payment. Take the (modified) example of jrandomhacker:
a=-5 b=25 c=-20 d=3 e=-2 f=-1 (sum should be zero!)
c -> b 20.
result: a=-5 b=5 c=0 d=3 e=-2 f=-1
a -> b 5
result: a=0 b=0 c=0 d=3 e=-2 f=-1
e -> d 2
result a=0 b=0 c=0 d=1 e=0 f=-1
f -> d 1
Now, everyone is satisfied and no one is bothered by making two or more payments. As you can see, it is possible that one person recieves more than one payment (person d in this example).
I have designed a solution using a somewhat different approach to the ones that have been mentioned here. It uses a linear programming formulation of the problem, drawing from the Compressed Sensing literature, especially from this work by Donoho and Tanner (2005).
I have written a blog post describing this approach, along with a tiny R package that implements it. I would love to get some feedback from this community.
Well, the first step is to totally ignore the transactions. Just add them up. All you actually need to know is the net amount of debt a person owes/owns.
You could very easily find transactions by then creating a crazy flow graph and finding max flow. Then a min cut...
Some partial elaboration:
There is a source node, a sink node, and a node for each person. There will be an edge between every pair of nodes except no edge between source node and sink node. Edges between people have infinite capacity in both directions. Edges coming from source node to person have capacity equal to the net debt of the person (0 if they have no net debt). Edges going from person node to sink node have capacity equal to the net amount of money that person is owed (0 if they have no net owed).
Apply max flow and/or min cut will give you a set of transfers. The actual flow amount will be how much money will be transfered.
Only if someone owes more than 2 people, whom also owe to the same set, can you reduce the number of transactions from the simple set.
That is, the simple set is just find each balance and repay it. That's no more than N! transactions.
If A owes B and C, and some subset of B C owe each other, so B owes C, then instead of: A -> B, A -> C (3 transactions). You'd use: A -> B, B -> C (2 transactions).
So in other words you are building a directed graph and you want to trim vertices on order to maximize path length and minimize total edges.
Sorry, I don't have an algorithm for you.
You should be able to solve this in O(n) by first determining how much each person owes and is owed. Transfer the debts of anyone who owes less than he is owed to his debtors (thus turning that person into an end point). Repeat until you can't transfer any more debts.
This is the code I wrote to solve this type of a problem in Java. I am not 100% sure if this gives the least number of transactions. The code's clarity and structure can be improved a lot.
In this example:
Sarah spent $400 on car rental. The car was used by Sarah, Bob, Alice
and John.
John spent $100 on groceries. The groceries were consumed by Bob and
Alice.
import java.util.*;
public class MoneyMinTransactions {
static class Expense{
String spender;
double amount;
List<String> consumers;
public Expense(String spender, double amount, String... consumers){
this.spender = spender;
this.amount = amount;
this.consumers = Arrays.asList(consumers);
}
}
static class Owed{
String name;
double amount;
public Owed(String name, double amount){
this.name = name;
this.amount = amount;
}
}
public static void main(String[] args){
List<Expense> expenseList = new ArrayList<>();
expenseList.add(new Expense("Sarah", 400, "Sarah", "John", "Bob", "Alice"));
expenseList.add(new Expense("John", 100, "Bob", "Alice"));
//make list of who owes how much.
Map<String, Double> owes = new HashMap<>();
for(Expense e:expenseList){
double owedAmt = e.amount/e.consumers.size();
for(String c : e.consumers){
if(!e.spender.equals(c)){
if(owes.containsKey(c)){
owes.put(c, owes.get(c) + owedAmt);
}else{
owes.put(c, owedAmt);
}
if(owes.containsKey(e.spender)){
owes.put(e.spender, owes.get(e.spender) + (-1 * owedAmt));
}else{
owes.put(e.spender, (-1 * owedAmt));
}
}
}
}
//make transactions.
// We need to settle all the negatives with positives. Make list of negatives. Order highest owed (i.e. the lowest negative) to least owed amount.
List<Owed> owed = new ArrayList<>();
for(String s : owes.keySet()){
if(owes.get(s) < 0){
owed.add(new Owed(s, owes.get(s)));
}
}
Collections.sort(owed, new Comparator<Owed>() {
#Override
public int compare(Owed o1, Owed o2) {
return Double.compare(o1.amount, o2.amount);
}
});
//take the highest negative, settle it with the best positive match:
// 1. a positive that is equal to the absolute negative amount is the best match.
// 2. the greatest positive value is the next best match.
// todo not sure if this matching strategy gives the least number of transactions.
for(Owed owedPerson: owed){
while(owes.get(owedPerson.name) != 0){
double negAmt = owes.get(owedPerson.name);
//get the best person to settle with
String s = getBestMatch(negAmt, owes);
double posAmt = owes.get(s);
if(posAmt > Math.abs(negAmt)){
owes.put(owedPerson.name, 0.0);
owes.put(s, posAmt - Math.abs(negAmt));
System.out.println(String.format("%s paid %s to %s", s, Double.toString((posAmt - Math.abs(negAmt))), owedPerson.name));
}else{
owes.put(owedPerson.name, -1 * (Math.abs(negAmt) - posAmt));
owes.put(s, 0.0);
System.out.println(String.format("%s paid %s to %s", s, Double.toString(posAmt), owedPerson.name));
}
}
}
}
private static String getBestMatch(double negAmount, Map<String, Double> owes){
String greatestS = null;
double greatestAmt = -1;
for(String s: owes.keySet()){
double amt = owes.get(s);
if(amt > 0){
if(amt == Math.abs(negAmount)){
return s;
}else if(greatestS == null || amt > greatestAmt){
greatestAmt = amt;
greatestS = s;
}
}
}
return greatestS;
}
}
The most optimal solution will require looking ahead to find equal pairs of debtors/creditors to minimize the total number of transactions.
The non-optimized solution is not too hard:
Sum up each person's total credit and debits
Match any two people (it actually does not need to be a debtor and a creditor)
One of these people will owe/be due $A, one will be owe/be due $B
Transfer the lower of |$A| and |$B| so that one person goes to $0
Go back to Step 2 and repeat until all $0
So note you always get one person to zero. With N people, on round (N-1), you will have +$X and -$X (a perfect match) and you set two people to zero. So this is always done in, at most, (N-1) rounds.
To optimize as an 'easy' step, you can always pair up people as soon as you see them That is, someone with +$X and someone with -$X. This alteration should cover >99% of all cases. So you get (N-1) rounds but sometimes (N-2), (N-3), etc if you happen upon a pair of +$X and -$X - you match them up for that round.
However, I have found that a truly optimal solution require (I believe) polynomial-time complexity as you need to run scenarios to try to force pairs.
Example in picture form
Simple solution - pick, say, highest creditor (+$) and lowest debtor (-$). Max of 5 rounds since Round 5 always has a match.
Round 1 Round 2 Round 3 Round 4 Round 5
a $100
b $ 90 $90
c $ 35 $35 $ 35 $ 10
d $ 10 $10 $ 10 $ 10 $ 10
e -$110 -$110 -$ 20 -$ 20 -$ 10
f -$125 -$25 -$ 25
From f e f e e
To a b c c d
Amount $100 $90 $25 $10 $10
Here is us forcing a match by altering Round 1 - we need an automated way of doing this
Round 1 Round 2 Round 3 Round 4
a $100
b $ 90 $ 90 $ 90
c $ 35 $ 35 $ 35 $ 35
d $ 10 $ 10
e -$110 -$ 10
f -$125 -$125 -$125 -$ 35
From e e f f
To a d b c
Amount $100 $10 $90 $35
Another example, showing how in some cases forgoing a "settle up at least one person" round may be an advantage. It doesn't beat the example above, but one could see with enough people, setting up chain reaction of pairs may result in that being optimal.
Round 1 Round 2 Round 3 Round 4
a $ 40 $ 55
b $ 20 $ 5 $ 5
c $ 4 $ 4 $ 4 $ 4
d -$ 4 -$ 4 -$ 4 -$ 4
e -$ 5 -$ 5 -$ 5
f -$ 55 -$ 55
From a f e d
To b a b c
Amount $15 $55 $5 $4
If you take states as nodes of graph then you will be able to use shortest path algorithm to know the answer.

Algorithm: Minimum number of Transanctions [duplicate]

This is kind of more generic question, isn't language-specific. More about idea and algorithm to use.
The system is as follows:
It registers small loans between groups of friends. Alice and Bill are going to lunch, Bill's card isn't working, so Alice pays for his meal, $10.
The next day Bill and Charles meet each other on a railway station, Charles has no money for ticket, so Bill buys him one, for $5.
Later that day Alice borrows $5 from Charles and $1 from Bill to buy her friend a gift.
Now, assuming they all registered that transactions in the system, it looks like this:
Alice -> Bill $10
Bill -> Alice $1
Bill -> Charles $5
Charles -> Alice $5
So, now, only thing that needs to be done is Bill giving Alice $4 (he gave her $1 and Charles transferred his $5 to Alice already) and they're at the initial state.
If we scale that to many different people, having multiple transaction, what would be the best algorithm to get as little transactions as possible?
This actually looks like a job that the double entry accounting concept could help with.
Your transactions could be structured as bookkeeping entries thus:
Alice Bill Charles Balance
Alice -> Bill $10 10 10- 0 0
Bill -> Alice $1 9 9- 0 0
Bill -> Charles $5 9 4- 5- 0
Charles -> Alice $5 4 4- 0 0
And there you have it. At each transaction, you credit one ledger account and debit another so that the balance is always zero. At at the end, you simply work out the minimal number transactions to be applied to each account to return it to zero.
For this simple case, it's a simple $4 transfer from Bill to Alice. What you need to do is to reduce at least one account (but preferably two) to zero for every transaction added. Let's say you had the more complicated:
Alice Bill Charles Balance
Alice -> Bill $10 10 10- 0 0
Bill -> Alice $1 9 9- 0 0
Bill -> Charles $5 9 4- 5- 0
Charles -> Alice $5 4 4- 0 0
Charles -> Bill $1 4 5- 1 0
Then the transactions needed would be:
Bill -> Alice $4 0 1- 1 0
Bill -> Charles $1 0 0 0 0
Unfortunately, there are some states where this simple greedy strategy does not generate the best solution (kudos to j_random_hacker for pointing this out). One example is:
Alan Bill Chas Doug Edie Fred Bal
Bill->Alan $5 5- 5 0 0 0 0 0
Bill->Chas $20 5- 25 20- 0 0 0 0
Doug->Edie $2 5- 25 20- 2 2- 0 0
Doug->Fred $1 5- 25 20- 3 2- 1- 0
Clearly, this could be reversed in four moves (since four moves is all it took to get there) but, if you choose your first move unwisely (Edie->Bill $2), five is the minimum you'll get away with.
You can solve this particular problem with the following rules:
(1) if you can wipe out two balances, do it.
(2) otherwise if you can wipe out one balance and set yourself up to wipe out two in the next move, do it.
(3) otherwise, wipe out any one balance.
That would result in the following sequence:
(a) [1] not applicable, [2] can be achieved with Alan->Bill $5.
(b) [1] can be done with Chas->Bill $20.
(c) and (d), similar reasoning with Doug, Edie and Fred, for four total moves.
However, that works simply because of the small number of possibilities. As the number of people rises and the group inter-relations becomes more complex, you'll most likely need an exhaustive search to find the minimum number of moves required (basically the rules 1, 2 and 3 above but expanded to handle more depth).
I think that is what will be required to give you the smallest number of transactions in all circumstances. However, it may be that that's not required for the best answer (best, in this case, meaning maximum "bang per buck"). It may be that even the basic 1/2/3 rule set will give you a good-enough answer for your purposes.
Intuitively, this sounds like an NP-complete problem (it reduces to a problem very like bin packing), however the following algorithm (a modified form of bin packing) should be pretty good (no time for a proof, sorry).
Net out everyone's positions, i.e. from your example above:
Alice = $4
Bill = $-4
Charles = $0
Sort all net creditors from highest to lowest, and all debtors from lowest to highest, then match by iterating over the lists.
At some point you might need to split a person's debts to net everything out - here it is probably best to split into the biggest chunks possible (i.e. into the bins with the most remaining space first).
This will take something like O(n log n) (again, proper proof needed).
See the Partition Problem and Bin Packing for more information (the former is a very similar problem, and if you limit yourself to fixed precision transactions, then it is equivalent - proof needed of course).
I have created an Android app which solves this problem. You can input expenses during the trip, it even recommends you "who should pay next". At the end it calculates "who should send how much to whom". My algorithm calculates minimum required number of transactions and you can setup "transaction tolerance" which can reduce transactions even further (you don't care about $1 transactions) Try it out, it's called Settle Up:
https://market.android.com/details?id=cz.destil.settleup
Description of my algorithm:
I have basic algorithm which solves the problem with n-1 transactions, but it's not optimal. It works like this: From payments, I compute balance for each member. Balance is what he paid minus what he should pay. I sort members according to balance increasingly. Then I always take the poorest and richest and transaction is made. At least one of them ends up with zero balance and is excluded from further calculations. With this, number of transactions cannot be worse than n-1. It also minimizes amount of money in transactions. But it's not optimal, because it doesn't detect subgroups which can settle up internally.
Finding subgroups which can settle up internally is hard. I solve it by generating all combinations of members and checking if sum of balances in subgroup equals zero. I start with 2-pairs, then 3-pairs ... (n-1)pairs. Implementations of combination generators are available. When I find a subgroup, I calculate transactions in the subgroup using basic algorithm described above. For every found subgroup, one transaction is spared.
The solution is optimal, but complexity increases to O(n!). This looks terrible but the trick is there will be just small number of members in reality. I have tested it on Nexus One (1 Ghz procesor) and the results are: until 10 members: <100 ms, 15 members: 1 s, 18 members: 8 s, 20 members: 55 s. So until 18 members the execution time is fine. Workaround for >15 members can be to use just the basic algorithm (it's fast and correct, but not optimal).
Source code:
Source code is available inside a report about algorithm written in Czech. Source code is at the end and it's in English:
http://www.settleup.info/files/master-thesis-david-vavra.pdf
I found a practical solution which I implemented in Excel:
find out who ows the most
let that person pay the complete amount he ows to the one who should get the most
that makes the first person zero
repeat this proces taken into account that one of (n-1) persons has a changed amount
It should result in a maximum of (n-1) transfers and the nice thing about it is that no one is doing more than one payment. Take the (modified) example of jrandomhacker:
a=-5 b=25 c=-20 d=3 e=-2 f=-1 (sum should be zero!)
c -> b 20.
result: a=-5 b=5 c=0 d=3 e=-2 f=-1
a -> b 5
result: a=0 b=0 c=0 d=3 e=-2 f=-1
e -> d 2
result a=0 b=0 c=0 d=1 e=0 f=-1
f -> d 1
Now, everyone is satisfied and no one is bothered by making two or more payments. As you can see, it is possible that one person recieves more than one payment (person d in this example).
I have designed a solution using a somewhat different approach to the ones that have been mentioned here. It uses a linear programming formulation of the problem, drawing from the Compressed Sensing literature, especially from this work by Donoho and Tanner (2005).
I have written a blog post describing this approach, along with a tiny R package that implements it. I would love to get some feedback from this community.
Well, the first step is to totally ignore the transactions. Just add them up. All you actually need to know is the net amount of debt a person owes/owns.
You could very easily find transactions by then creating a crazy flow graph and finding max flow. Then a min cut...
Some partial elaboration:
There is a source node, a sink node, and a node for each person. There will be an edge between every pair of nodes except no edge between source node and sink node. Edges between people have infinite capacity in both directions. Edges coming from source node to person have capacity equal to the net debt of the person (0 if they have no net debt). Edges going from person node to sink node have capacity equal to the net amount of money that person is owed (0 if they have no net owed).
Apply max flow and/or min cut will give you a set of transfers. The actual flow amount will be how much money will be transfered.
Only if someone owes more than 2 people, whom also owe to the same set, can you reduce the number of transactions from the simple set.
That is, the simple set is just find each balance and repay it. That's no more than N! transactions.
If A owes B and C, and some subset of B C owe each other, so B owes C, then instead of: A -> B, A -> C (3 transactions). You'd use: A -> B, B -> C (2 transactions).
So in other words you are building a directed graph and you want to trim vertices on order to maximize path length and minimize total edges.
Sorry, I don't have an algorithm for you.
You should be able to solve this in O(n) by first determining how much each person owes and is owed. Transfer the debts of anyone who owes less than he is owed to his debtors (thus turning that person into an end point). Repeat until you can't transfer any more debts.
This is the code I wrote to solve this type of a problem in Java. I am not 100% sure if this gives the least number of transactions. The code's clarity and structure can be improved a lot.
In this example:
Sarah spent $400 on car rental. The car was used by Sarah, Bob, Alice
and John.
John spent $100 on groceries. The groceries were consumed by Bob and
Alice.
import java.util.*;
public class MoneyMinTransactions {
static class Expense{
String spender;
double amount;
List<String> consumers;
public Expense(String spender, double amount, String... consumers){
this.spender = spender;
this.amount = amount;
this.consumers = Arrays.asList(consumers);
}
}
static class Owed{
String name;
double amount;
public Owed(String name, double amount){
this.name = name;
this.amount = amount;
}
}
public static void main(String[] args){
List<Expense> expenseList = new ArrayList<>();
expenseList.add(new Expense("Sarah", 400, "Sarah", "John", "Bob", "Alice"));
expenseList.add(new Expense("John", 100, "Bob", "Alice"));
//make list of who owes how much.
Map<String, Double> owes = new HashMap<>();
for(Expense e:expenseList){
double owedAmt = e.amount/e.consumers.size();
for(String c : e.consumers){
if(!e.spender.equals(c)){
if(owes.containsKey(c)){
owes.put(c, owes.get(c) + owedAmt);
}else{
owes.put(c, owedAmt);
}
if(owes.containsKey(e.spender)){
owes.put(e.spender, owes.get(e.spender) + (-1 * owedAmt));
}else{
owes.put(e.spender, (-1 * owedAmt));
}
}
}
}
//make transactions.
// We need to settle all the negatives with positives. Make list of negatives. Order highest owed (i.e. the lowest negative) to least owed amount.
List<Owed> owed = new ArrayList<>();
for(String s : owes.keySet()){
if(owes.get(s) < 0){
owed.add(new Owed(s, owes.get(s)));
}
}
Collections.sort(owed, new Comparator<Owed>() {
#Override
public int compare(Owed o1, Owed o2) {
return Double.compare(o1.amount, o2.amount);
}
});
//take the highest negative, settle it with the best positive match:
// 1. a positive that is equal to the absolute negative amount is the best match.
// 2. the greatest positive value is the next best match.
// todo not sure if this matching strategy gives the least number of transactions.
for(Owed owedPerson: owed){
while(owes.get(owedPerson.name) != 0){
double negAmt = owes.get(owedPerson.name);
//get the best person to settle with
String s = getBestMatch(negAmt, owes);
double posAmt = owes.get(s);
if(posAmt > Math.abs(negAmt)){
owes.put(owedPerson.name, 0.0);
owes.put(s, posAmt - Math.abs(negAmt));
System.out.println(String.format("%s paid %s to %s", s, Double.toString((posAmt - Math.abs(negAmt))), owedPerson.name));
}else{
owes.put(owedPerson.name, -1 * (Math.abs(negAmt) - posAmt));
owes.put(s, 0.0);
System.out.println(String.format("%s paid %s to %s", s, Double.toString(posAmt), owedPerson.name));
}
}
}
}
private static String getBestMatch(double negAmount, Map<String, Double> owes){
String greatestS = null;
double greatestAmt = -1;
for(String s: owes.keySet()){
double amt = owes.get(s);
if(amt > 0){
if(amt == Math.abs(negAmount)){
return s;
}else if(greatestS == null || amt > greatestAmt){
greatestAmt = amt;
greatestS = s;
}
}
}
return greatestS;
}
}
The most optimal solution will require looking ahead to find equal pairs of debtors/creditors to minimize the total number of transactions.
The non-optimized solution is not too hard:
Sum up each person's total credit and debits
Match any two people (it actually does not need to be a debtor and a creditor)
One of these people will owe/be due $A, one will be owe/be due $B
Transfer the lower of |$A| and |$B| so that one person goes to $0
Go back to Step 2 and repeat until all $0
So note you always get one person to zero. With N people, on round (N-1), you will have +$X and -$X (a perfect match) and you set two people to zero. So this is always done in, at most, (N-1) rounds.
To optimize as an 'easy' step, you can always pair up people as soon as you see them That is, someone with +$X and someone with -$X. This alteration should cover >99% of all cases. So you get (N-1) rounds but sometimes (N-2), (N-3), etc if you happen upon a pair of +$X and -$X - you match them up for that round.
However, I have found that a truly optimal solution require (I believe) polynomial-time complexity as you need to run scenarios to try to force pairs.
Example in picture form
Simple solution - pick, say, highest creditor (+$) and lowest debtor (-$). Max of 5 rounds since Round 5 always has a match.
Round 1 Round 2 Round 3 Round 4 Round 5
a $100
b $ 90 $90
c $ 35 $35 $ 35 $ 10
d $ 10 $10 $ 10 $ 10 $ 10
e -$110 -$110 -$ 20 -$ 20 -$ 10
f -$125 -$25 -$ 25
From f e f e e
To a b c c d
Amount $100 $90 $25 $10 $10
Here is us forcing a match by altering Round 1 - we need an automated way of doing this
Round 1 Round 2 Round 3 Round 4
a $100
b $ 90 $ 90 $ 90
c $ 35 $ 35 $ 35 $ 35
d $ 10 $ 10
e -$110 -$ 10
f -$125 -$125 -$125 -$ 35
From e e f f
To a d b c
Amount $100 $10 $90 $35
Another example, showing how in some cases forgoing a "settle up at least one person" round may be an advantage. It doesn't beat the example above, but one could see with enough people, setting up chain reaction of pairs may result in that being optimal.
Round 1 Round 2 Round 3 Round 4
a $ 40 $ 55
b $ 20 $ 5 $ 5
c $ 4 $ 4 $ 4 $ 4
d -$ 4 -$ 4 -$ 4 -$ 4
e -$ 5 -$ 5 -$ 5
f -$ 55 -$ 55
From a f e d
To b a b c
Amount $15 $55 $5 $4
If you take states as nodes of graph then you will be able to use shortest path algorithm to know the answer.

Who owes who money optimization

Say you have n people, each who owe each other money. In general it should be possible to reduce the amount of transactions that need to take place. i.e. if X owes Y £4 and Y owes X £8, then Y only needs to pay X £4 (1 transaction instead of 2).
This becomes harder when X owes Y, but Y owes Z who owes X as well. I can see that you can easily calculate one particular cycle. It helps for me when I think of it as a fully connected graph, with the edges being the amount each person owes.
Problem seems to be NP-complete, but what kind of optimisation algorithm could I make, nevertheless, to reduce the total amount of transactions? Doesn't have to be that efficient, as N is quite small for me.
Edit:
The purpose of this problem would be to be able to have in the accounting system something that can say to each person when they log in "You can remove M amount of transactions by simply paying someone X amount, and someone else Y amount". Hence the bank solution (though optimal if everyone is paying at the same time) cannot really be used here.
Are people required to clear their debts by paying somebody that they actually owe money to personally? If not, the following seems to work suspiciously easily:
For each person, work out the net amount they should pay, or should receive.
Have somebody who owes money net pay somebody who should receive money net min(amount owed, amount to be received). After this, at least one of the two participants owes nothing and should receive nothing, and so can be removed from the problem.
Assuming I have missed something, what are the constraints that apply (or gross error made)?
I have created an Android app which solves this problem. You can input expenses during the trip, it even recommends you "who should pay next". At the end it calculates "who should send how much to whom". My algorithm calculates minimum required number of transactions and you can setup "transaction tolerance" which can reduce transactions even further (you don't care about $1 transactions) Try it out, it's called Settle Up:
https://market.android.com/details?id=cz.destil.settleup
Description of my algorithm:
I have basic algorithm which solves the problem with n-1 transactions, but it's not optimal. It works like this: From payments, I compute balance for each member. Balance is what he paid minus what he should pay. I sort members according to balance increasingly. Then I always take the poorest and richest and transaction is made. At least one of them ends up with zero balance and is excluded from further calculations. With this, number of transactions cannot be worse than n-1. It also minimizes amount of money in transactions. But it's not optimal, because it doesn't detect subgroups which can settle up internally.
Finding subgroups which can settle up internally is hard. I solve it by generating all combinations of members and checking if sum of balances in subgroup equals zero. I start with 2-pairs, then 3-pairs ... (n-1)pairs. Implementations of combination generators are available. When I find a subgroup, I calculate transactions in the subgroup using basic algorithm described above. For every found subgroup, one transaction is spared.
The solution is optimal, but complexity increases to O(n!). This looks terrible but the trick is there will be just small number of members in reality. I have tested it on Nexus One (1 Ghz procesor) and the results are: until 10 members: <100 ms, 15 members: 1 s, 18 members: 8 s, 20 members: 55 s. So until 18 members the execution time is fine. Workaround for >15 members can be to use just the basic algorithm (it's fast and correct, but not optimal).
Source code:
Source code is available inside a report about algorithm written in Czech. Source code is at the end and it's in English:
http://settleup.destil.cz/report.pdf
Nominate one person arbitrarily to be the banker.
Each other person transfers the sum of all the outgoing transactions minus the incoming transactions (so either deposits or withdraws) to that person.
There will be a maximum of (n-1) transactions, which is pretty small. It is fast. It is simple.
Given that everyone who transfers money will have to be involved in a transaction anyway*, it is bounded to be at worst twice the optimal case.**
* The exception is the banker themselves. A quick optimisation is to ensure the nominated banker is not someone who holds a neutral position.
** Explaining my upper bound logic further:
Suppose the optimal case is A gives $1 to B, and C gives $1 to D, and E is neutral = two transactions.
Then with this logic, if E is the nominated banker, A gives $1 to E, E gives $1 to B, C gives $1 to E and E gives $1 to D = four transactions.
With the optimisation, making sure you don't choose a neutral person for banker, select A instead.
A gives $1 to B, C gives $1 to A. A gives $1 to D = three transactions.
for each debt in debts
debt.creditor.owed -= debt.amount
debt.deptor.owed += debt.amount
end
for each person in persons
if person.owed > 0 then
deptors.add person
else if person.owed < 0 then
creditors.add person
end
end
deptors.sort_by_owed_desc
creditor.sort_by_owed_asc
for each debtor in deptors
while debtor.owed > 0
creditor = creditors.top
amount = min( debtor.owed, -creditor.owed)
creditor.owed += amount
debtor.owed -= amount
if creditor.owed == 0 then
creditors.remove_top
end
write debtor.name " owes " creditor.name " " amount "€"
end
end
Just thinking about it I'd start by looking at each cycle in the directed graph and reducing each edge in the cycle by the value of the minimum edge in the cycle, then remove the minimum edge altogether. Rinse and repeat.
Here's the Python solution I used; it's the same idea as Gunner's post, with a few line changes:
for i in N:
for j in N:
if i!=j and owes[i][j] > owes[j][i]:
owes[i][j] -= owes[j][i]
owes[j][i] = 0
for k in N:
for i in N:
for j in N:
if k == i or i == j or k == j:
continue
if owes[j][k] > owes[i][j]:
owes[i][k] += owes[i][j]
owes[j][k] -= owes[i][j]
owes[i][j] = 0;
Works a treat.
You can test it with i.e.:
owes = [[0,2,11], [4,0,7], [2,3,0]]
N = range(len(owes))
I think you need to build a different data structure ( a tree, each time one person is the root node) that will check for each person how many "transaction" can you "kill", than, choose the best one, make the cycle, and rebuild it again.it is not o(N), I Think it's N^2 though, and it will not give you the best result. it is just a strategy.
This problem may be tackled with the Warshall algorithm.
for(i=0;i<n;i++)
for(j=0;j<n;j++)
if ( i!= j && owes[i][j] > owes[j][i] )
owes[i][j] -= (owes[i][j] - owes[j][i]), owes[j][i] = 0;
for(k=0;k<n;k++)
for(i=0;i<n;i++)
for(j=0;j<n;j++)
{
if( k == i || i == j || k == j ) continue;
if ( owes[j][k] > owes[i][j] )
{
int diff = owes[j][k] - owes[i][j];
owes[i][j] = 0;
owes[i][k ] += diff;
owes[j][k] -= diff;
}
}
After the algorithm finishes, the total number of transactions required would be the number of positive entries in the owes table.
I have not verified yet whether the algorithm will work, based on nature of the problem it may work. Solution is O(N^3).
I think you must remove all cicles reducing edges by minimal edge value and removingedges with value 0. After it you will get graph withouth cicles. I think you must find vertexes, wich have no pointers to them (man's wich owes only others money). This man's must pay money, beacouse there is no one to pay the money for them. So my point is that you must find somehow who they must pay.
I have a solution to the problem written in matlab. It is based on a matrix of who owes who what. The number in the (i,j) means that person j owes person i the number. E.g.
B owes A 2
and A owes B 1
of course in this case it is trivial that B should just give A 1
This becomes more complex with more entries. However, with the algorithm i wrote i can guarantee that no more than N-1 transactions occurs where N is the number of persones 2 in this case.
Here is the code i wrote.
function out = whooweswho(matrix)
%input sanitation
if ~isposintscalar(matrix)
[N M] = size(matrix);
if N ~= M
error('Matrix must be square');
end
for i=1:N
if matrix(N,N) ~= 0
error('Matrix must have zero on diagonals');
end
end
else
%construction of example matrix if input is a positive scalar
disp('scalar input: Showing example of NxN matrix randomly filled');
N = matrix;
matrix = round(10*N*rand(N,N)).*(ones(N,N)-eye(N))
end
%construction of vector containing each persons balance
net = zeros(N,1);
for i=1:N
net(i) = sum(matrix(i,:))-sum(matrix(:,i));
end
%zero matrix, so it can be used for the result
matrix = zeros(size(matrix));
%sum(net) == 0 always as no money dissappears. So if min(net) == 0 it
%implies that all balances are zero and we are done.
while min(net) ~= 0
%find the poorest and the richest.
[rec_amount reciever] = max(net);
[give_amount giver] = min(net);
%balance so at least one of them gets zero balance.
amount =min(abs(rec_amount),abs(give_amount));
net(reciever) = net(reciever) - amount;
net(giver) = net(giver) + amount;
%store result in matrix.
matrix(reciever,giver) = amount;
end
%output equivalent matrix of input just reduced.
out = matrix;
end

Algorithm to share/settle expenses among a group

I am looking forward for an algorithm for the below problem.
Problem: There will be a set of people who owe each other some money or none. Now, I need an algorithm (the best and neat) to settle expense among this group.
Person AmtSpent
------ ---------
A 400
B 1000
C 100
Total 1500
Now, expense per person is 1500/3 = 500. Meaning B to give A 100. B to give C 400. I know, I can start with the least spent amount and work forward.
Can some one point me the best one if you have.
To sum up,
Find the total expense, and expense per head.
Find the amount each owe or outstanding (-ve denote outstanding).
Start with the least +ve amount. Allocate it to the -ve amount.
Keep repeating step 3, until you run out of -ve amount.
s. Move to next bigger +ve number. Keep repeating 3 & 4 until there are +ve numbers.
Or is there any better way to do?
The best way to get back to zero state (minimum number of transactions) was covered in this question here.
I have created an Android app which solves this problem. You can input expenses during the trip, it even recommends you "who should pay next". At the end it calculates "who should send how much to whom". My algorithm calculates minimum required number of transactions and you can setup "transaction tolerance" which can reduce transactions even further (you don't care about $1 transactions) Try it out, it's called Settle Up:
https://market.android.com/details?id=cz.destil.settleup
Description of my algorithm:
I have basic algorithm which solves the problem with n-1 transactions, but it's not optimal. It works like this: From payments, I compute balance for each member. Balance is what he paid minus what he should pay. I sort members according to balance increasingly. Then I always take the poorest and richest and transaction is made. At least one of them ends up with zero balance and is excluded from further calculations. With this, number of transactions cannot be worse than n-1. It also minimizes amount of money in transactions. But it's not optimal, because it doesn't detect subgroups which can settle up internally.
Finding subgroups which can settle up internally is hard. I solve it by generating all combinations of members and checking if sum of balances in subgroup equals zero. I start with 2-pairs, then 3-pairs ... (n-1)pairs. Implementations of combination generators are available. When I find a subgroup, I calculate transactions in the subgroup using basic algorithm described above. For every found subgroup, one transaction is spared.
The solution is optimal, but complexity increases to O(n!). This looks terrible but the trick is there will be just small number of members in reality. I have tested it on Nexus One (1 Ghz procesor) and the results are: until 10 members: <100 ms, 15 members: 1 s, 18 members: 8 s, 20 members: 55 s. So until 18 members the execution time is fine. Workaround for >15 members can be to use just the basic algorithm (it's fast and correct, but not optimal).
Source code:
Source code is available inside a report about algorithm written in Czech. Source code is at the end and it's in English:
https://web.archive.org/web/20190214205754/http://www.settleup.info/files/master-thesis-david-vavra.pdf
You have described it already. Sum all the expenses (1500 in your case), divide by number of people sharing the expense (500). For each individual, deduct the contributions that person made from the individual share (for person A, deduct 400 from 500). The result is the net that person "owes" to the central pool. If the number is negative for any person, the central pool "owes" the person.
Because you have already described the solution, I don't know what you are asking.
Maybe you are trying to resolve the problem without the central pool, the "bank"?
I also don't know what you mean by "start with the least spent amount and work forward."
Javascript solution to the accepted algorithm:
const payments = {
John: 400,
Jane: 1000,
Bob: 100,
Dave: 900,
};
function splitPayments(payments) {
const people = Object.keys(payments);
const valuesPaid = Object.values(payments);
const sum = valuesPaid.reduce((acc, curr) => curr + acc);
const mean = sum / people.length;
const sortedPeople = people.sort((personA, personB) => payments[personA] - payments[personB]);
const sortedValuesPaid = sortedPeople.map((person) => payments[person] - mean);
let i = 0;
let j = sortedPeople.length - 1;
let debt;
while (i < j) {
debt = Math.min(-(sortedValuesPaid[i]), sortedValuesPaid[j]);
sortedValuesPaid[i] += debt;
sortedValuesPaid[j] -= debt;
console.log(`${sortedPeople[i]} owes ${sortedPeople[j]} $${debt}`);
if (sortedValuesPaid[i] === 0) {
i++;
}
if (sortedValuesPaid[j] === 0) {
j--;
}
}
}
splitPayments(payments);
/*
C owes B $400
C owes D $100
A owes D $200
*/
I have recently written a blog post describing an approach to solve the settlement of expenses between members of a group where potentially everybody owes everybody else, such that the number of payments needed to settle the debts is the least possible. It uses a linear programming formulation. I also show an example using a tiny R package that implements the solution.
I had to do this after a trip with my friends, here's a python3 version:
import numpy as np
import pandas as pd
# setup inputs
people = ["Athos", "Porthos", "Aramis"] # friends names
totals = [300, 150, 90] # total spent per friend
# compute matrix
total_spent = np.array(totals).reshape(-1,1)
share = total_spent / len(totals)
mat = (share.T - share).clip(min=0)
# create a readable dataframe
column_labels = [f"to_{person}" for person in people]
index_labels = [f"{person}_owes" for person in people]
df = pd.DataFrame(data=mat, columns=column_labels, index=index_labels)
df.round(2)
Returns this dataframe:
to_Athos
to_Porthos
to_Aramis
Athos_owes
0
0
0
Porthos_owes
50
0
0
Aramis_owes
70
20
0
Read it like: "Porthos owes $50 to Athos" ....
This isn't the optimized version, this is the simple version, but it's simple code and may work in many situations.
I'd like to make a suggestion to change the core parameters, from a UX-standpoint if you don't mind terribly.
Whether its services or products being expensed amongst a group, sometimes these things can be shared. For example, an appetizer, or private/semi-private sessions at a conference.
For things like an appetizer party tray, it's sort of implied that everyone has access but not necessarily that everyone had it. To charge each person to split the expense when say, only 30% of the people partook can cause contention when it comes to splitting the bill. Other groups of people might not care at all. So from an algorithm standpoint, you need to first decide which of these three choices will be used, probably per-expense:
Universally split
Split by those who partook, evenly
Split by proportion per-partaker
I personally prefer the second one in-general because it has the utility to handle whole-expense-ownership for expenses only used by one person, some of the people, and the whole group too. It also remedies the ethical question of proportional differences with a blanket generalization of, if you partook, you're paying an even split regardless of how much you actually personally had. As a social element, I would consider someone who had a "small sample" of something just to try it and then decided not to have anymore as a justification to remove that person from the people splitting the expense.
So, small-sampling != partaking ;)
Then you take each expense and iterate through the group of who partook in what, and atomically handle each of those items, and at the end provide a total per-person.
So in the end, you take your list of expenses and iterate through them with each person. At the end of the individual expense check, you take the people who partook and apply an even split of that expense to each person, and update each person's current split of the bill.
Pardon the pseudo-code:
list_of_expenses[] = getExpenseList()
list_of_agents_to_charge[] = getParticipantList()
for each expense in list_of_expenses
list_of_partakers[] = getPartakerList(expense)
for each partaker in list_of_partakers
addChargeToAgent(expense.price / list_of_partakers.size, list_of_agents_to_charge[partaker])
Then just iterate through your list_of_agents_to_charge[] and report each total to each agent.
You can add support for a tip by simply treating the tip like an additional expense to your list of expenses.
Straightforward, as you do in your text:
Returns expenses to be payed by everybody in the original array.
Negativ values: this person gets some back
Just hand whatever you owe to the next in line and then drop out. If you get some, just wait for the second round. When done, reverse the whole thing. After these two round everybody has payed the same amount.
procedure SettleDepth(Expenses: array of double);
var
i: Integer;
s: double;
begin
//Sum all amounts and divide by number of people
// O(n)
s := 0.0;
for i := Low(Expenses) to High(Expenses) do
s := s + Expenses[i];
s := s / (High(Expenses) - Low(Expenses));
// Inplace Change to owed amount
// and hand on what you owe
// drop out if your even
for i := High(Expenses) downto Low(Expenses)+1 do begin
Expenses[i] := s - Expenses[i];
if (Expenses[i] > 0) then begin
Expenses[i-1] := Expenses[i-1] + Expenses[i];
Expenses.Delete(i);
end else if (Expenses[i] = 0) then begin
Expenses.Delete(i);
end;
end;
Expenses[Low(Expenses)] := s - Expenses[Low(Expenses)];
if (Expenses[Low(Expenses)] = 0) then begin
Expenses.Delete(Low(Expenses));
end;
// hand on what you owe
for i := Low(Expenses) to High(Expenses)-1 do begin
if (Expenses[i] > 0) then begin
Expenses[i+1] := Expenses[i+1] + Expenses[i];
end;
end;
end;
The idea (similar to what is asked but with a twist/using a bit of ledger concept) is to use Pool Account where for each bill, members either pay to the Pool or get from the Pool.
e.g.
in below attached image, the Costco expenses are paid by Mr P and needs $93.76 from Pool and other members pay $46.88 to the pool.
There are obviously better ways to do it. But that would require running a NP time complexity algorithm which could really show down your application. Anyways, this is how I implemented the solution in java for my android application using Priority Queues:
class calculateTransactions {
public static void calculateBalances(debtors,creditors) {
// add members who are owed money to debtors priority queue
// add members who owe money to others to creditors priority queue
}
public static void calculateTransactions() {
results.clear(); // remove previously calculated transactions before calculating again
PriorityQueue<Balance> debtors = new PriorityQueue<>(members.size(),new BalanceComparator()); // debtors are members of the group who are owed money, balance comparator defined for max priority queue
PriorityQueue<Balance> creditors = new PriorityQueue<>(members.size(),new BalanceComparator()); // creditors are members who have to pay money to the group
calculateBalances(debtors,creditors);
/*Algorithm: Pick the largest element from debtors and the largest from creditors. Ex: If debtors = {4,3} and creditors={2,7}, pick 4 as the largest debtor and 7 as the largest creditor.
* Now, do a transaction between them. The debtor with a balance of 4 receives $4 from the creditor with a balance of 7 and hence, the debtor is eliminated from further
* transactions. Repeat the same thing until and unless there are no creditors and debtors.
*
* The priority queues help us find the largest creditor and debtor in constant time. However, adding/removing a member takes O(log n) time to perform it.
* Optimisation: This algorithm produces correct results but the no of transactions is not minimum. To minimize it, we could use the subset sum algorithm which is a NP problem.
* The use of a NP solution could really slow down the app! */
while(!creditors.isEmpty() && !debtors.isEmpty()) {
Balance rich = creditors.peek(); // get the largest creditor
Balance poor = debtors.peek(); // get the largest debtor
if(rich == null || poor == null) {
return;
}
String richName = rich.name;
BigDecimal richBalance = rich.balance;
creditors.remove(rich); // remove the creditor from the queue
String poorName = poor.name;
BigDecimal poorBalance = poor.balance;
debtors.remove(poor); // remove the debtor from the queue
BigDecimal min = richBalance.min(poorBalance);
// calculate the amount to be send from creditor to debtor
richBalance = richBalance.subtract(min);
poorBalance = poorBalance.subtract(min);
HashMap<String,Object> values = new HashMap<>(); // record the transaction details in a HashMap
values.put("sender",richName);
values.put("recipient",poorName);
values.put("amount",currency.charAt(5) + min.toString());
results.add(values);
// Consider a member as settled if he has an outstanding balance between 0.00 and 0.49 else add him to the queue again
int compare = 1;
if(poorBalance.compareTo(new BigDecimal("0.49")) == compare) {
// if the debtor is not yet settled(has a balance between 0.49 and inf) add him to the priority queue again so that he is available for further transactions to settle up his debts
debtors.add(new Balance(poorBalance,poorName));
}
if(richBalance.compareTo(new BigDecimal("0.49")) == compare) {
// if the creditor is not yet settled(has a balance between 0.49 and inf) add him to the priority queue again so that he is available for further transactions
creditors.add(new Balance(richBalance,richName));
}
}
}
}
I've created a React App that implements Bin-packing approach to split trip expenses among friends with least number of transactions.
Check out the TypeScript file SplitPaymentCalculator.ts implementing the same.
You can find the working app's link on the homepage of the repo.

What algorithm to use to determine minimum number of actions required to get the system to "Zero" state?

This is kind of more generic question, isn't language-specific. More about idea and algorithm to use.
The system is as follows:
It registers small loans between groups of friends. Alice and Bill are going to lunch, Bill's card isn't working, so Alice pays for his meal, $10.
The next day Bill and Charles meet each other on a railway station, Charles has no money for ticket, so Bill buys him one, for $5.
Later that day Alice borrows $5 from Charles and $1 from Bill to buy her friend a gift.
Now, assuming they all registered that transactions in the system, it looks like this:
Alice -> Bill $10
Bill -> Alice $1
Bill -> Charles $5
Charles -> Alice $5
So, now, only thing that needs to be done is Bill giving Alice $4 (he gave her $1 and Charles transferred his $5 to Alice already) and they're at the initial state.
If we scale that to many different people, having multiple transaction, what would be the best algorithm to get as little transactions as possible?
This actually looks like a job that the double entry accounting concept could help with.
Your transactions could be structured as bookkeeping entries thus:
Alice Bill Charles Balance
Alice -> Bill $10 10 10- 0 0
Bill -> Alice $1 9 9- 0 0
Bill -> Charles $5 9 4- 5- 0
Charles -> Alice $5 4 4- 0 0
And there you have it. At each transaction, you credit one ledger account and debit another so that the balance is always zero. At at the end, you simply work out the minimal number transactions to be applied to each account to return it to zero.
For this simple case, it's a simple $4 transfer from Bill to Alice. What you need to do is to reduce at least one account (but preferably two) to zero for every transaction added. Let's say you had the more complicated:
Alice Bill Charles Balance
Alice -> Bill $10 10 10- 0 0
Bill -> Alice $1 9 9- 0 0
Bill -> Charles $5 9 4- 5- 0
Charles -> Alice $5 4 4- 0 0
Charles -> Bill $1 4 5- 1 0
Then the transactions needed would be:
Bill -> Alice $4 0 1- 1 0
Bill -> Charles $1 0 0 0 0
Unfortunately, there are some states where this simple greedy strategy does not generate the best solution (kudos to j_random_hacker for pointing this out). One example is:
Alan Bill Chas Doug Edie Fred Bal
Bill->Alan $5 5- 5 0 0 0 0 0
Bill->Chas $20 5- 25 20- 0 0 0 0
Doug->Edie $2 5- 25 20- 2 2- 0 0
Doug->Fred $1 5- 25 20- 3 2- 1- 0
Clearly, this could be reversed in four moves (since four moves is all it took to get there) but, if you choose your first move unwisely (Edie->Bill $2), five is the minimum you'll get away with.
You can solve this particular problem with the following rules:
(1) if you can wipe out two balances, do it.
(2) otherwise if you can wipe out one balance and set yourself up to wipe out two in the next move, do it.
(3) otherwise, wipe out any one balance.
That would result in the following sequence:
(a) [1] not applicable, [2] can be achieved with Alan->Bill $5.
(b) [1] can be done with Chas->Bill $20.
(c) and (d), similar reasoning with Doug, Edie and Fred, for four total moves.
However, that works simply because of the small number of possibilities. As the number of people rises and the group inter-relations becomes more complex, you'll most likely need an exhaustive search to find the minimum number of moves required (basically the rules 1, 2 and 3 above but expanded to handle more depth).
I think that is what will be required to give you the smallest number of transactions in all circumstances. However, it may be that that's not required for the best answer (best, in this case, meaning maximum "bang per buck"). It may be that even the basic 1/2/3 rule set will give you a good-enough answer for your purposes.
Intuitively, this sounds like an NP-complete problem (it reduces to a problem very like bin packing), however the following algorithm (a modified form of bin packing) should be pretty good (no time for a proof, sorry).
Net out everyone's positions, i.e. from your example above:
Alice = $4
Bill = $-4
Charles = $0
Sort all net creditors from highest to lowest, and all debtors from lowest to highest, then match by iterating over the lists.
At some point you might need to split a person's debts to net everything out - here it is probably best to split into the biggest chunks possible (i.e. into the bins with the most remaining space first).
This will take something like O(n log n) (again, proper proof needed).
See the Partition Problem and Bin Packing for more information (the former is a very similar problem, and if you limit yourself to fixed precision transactions, then it is equivalent - proof needed of course).
I have created an Android app which solves this problem. You can input expenses during the trip, it even recommends you "who should pay next". At the end it calculates "who should send how much to whom". My algorithm calculates minimum required number of transactions and you can setup "transaction tolerance" which can reduce transactions even further (you don't care about $1 transactions) Try it out, it's called Settle Up:
https://market.android.com/details?id=cz.destil.settleup
Description of my algorithm:
I have basic algorithm which solves the problem with n-1 transactions, but it's not optimal. It works like this: From payments, I compute balance for each member. Balance is what he paid minus what he should pay. I sort members according to balance increasingly. Then I always take the poorest and richest and transaction is made. At least one of them ends up with zero balance and is excluded from further calculations. With this, number of transactions cannot be worse than n-1. It also minimizes amount of money in transactions. But it's not optimal, because it doesn't detect subgroups which can settle up internally.
Finding subgroups which can settle up internally is hard. I solve it by generating all combinations of members and checking if sum of balances in subgroup equals zero. I start with 2-pairs, then 3-pairs ... (n-1)pairs. Implementations of combination generators are available. When I find a subgroup, I calculate transactions in the subgroup using basic algorithm described above. For every found subgroup, one transaction is spared.
The solution is optimal, but complexity increases to O(n!). This looks terrible but the trick is there will be just small number of members in reality. I have tested it on Nexus One (1 Ghz procesor) and the results are: until 10 members: <100 ms, 15 members: 1 s, 18 members: 8 s, 20 members: 55 s. So until 18 members the execution time is fine. Workaround for >15 members can be to use just the basic algorithm (it's fast and correct, but not optimal).
Source code:
Source code is available inside a report about algorithm written in Czech. Source code is at the end and it's in English:
http://www.settleup.info/files/master-thesis-david-vavra.pdf
I found a practical solution which I implemented in Excel:
find out who ows the most
let that person pay the complete amount he ows to the one who should get the most
that makes the first person zero
repeat this proces taken into account that one of (n-1) persons has a changed amount
It should result in a maximum of (n-1) transfers and the nice thing about it is that no one is doing more than one payment. Take the (modified) example of jrandomhacker:
a=-5 b=25 c=-20 d=3 e=-2 f=-1 (sum should be zero!)
c -> b 20.
result: a=-5 b=5 c=0 d=3 e=-2 f=-1
a -> b 5
result: a=0 b=0 c=0 d=3 e=-2 f=-1
e -> d 2
result a=0 b=0 c=0 d=1 e=0 f=-1
f -> d 1
Now, everyone is satisfied and no one is bothered by making two or more payments. As you can see, it is possible that one person recieves more than one payment (person d in this example).
I have designed a solution using a somewhat different approach to the ones that have been mentioned here. It uses a linear programming formulation of the problem, drawing from the Compressed Sensing literature, especially from this work by Donoho and Tanner (2005).
I have written a blog post describing this approach, along with a tiny R package that implements it. I would love to get some feedback from this community.
Well, the first step is to totally ignore the transactions. Just add them up. All you actually need to know is the net amount of debt a person owes/owns.
You could very easily find transactions by then creating a crazy flow graph and finding max flow. Then a min cut...
Some partial elaboration:
There is a source node, a sink node, and a node for each person. There will be an edge between every pair of nodes except no edge between source node and sink node. Edges between people have infinite capacity in both directions. Edges coming from source node to person have capacity equal to the net debt of the person (0 if they have no net debt). Edges going from person node to sink node have capacity equal to the net amount of money that person is owed (0 if they have no net owed).
Apply max flow and/or min cut will give you a set of transfers. The actual flow amount will be how much money will be transfered.
Only if someone owes more than 2 people, whom also owe to the same set, can you reduce the number of transactions from the simple set.
That is, the simple set is just find each balance and repay it. That's no more than N! transactions.
If A owes B and C, and some subset of B C owe each other, so B owes C, then instead of: A -> B, A -> C (3 transactions). You'd use: A -> B, B -> C (2 transactions).
So in other words you are building a directed graph and you want to trim vertices on order to maximize path length and minimize total edges.
Sorry, I don't have an algorithm for you.
You should be able to solve this in O(n) by first determining how much each person owes and is owed. Transfer the debts of anyone who owes less than he is owed to his debtors (thus turning that person into an end point). Repeat until you can't transfer any more debts.
This is the code I wrote to solve this type of a problem in Java. I am not 100% sure if this gives the least number of transactions. The code's clarity and structure can be improved a lot.
In this example:
Sarah spent $400 on car rental. The car was used by Sarah, Bob, Alice
and John.
John spent $100 on groceries. The groceries were consumed by Bob and
Alice.
import java.util.*;
public class MoneyMinTransactions {
static class Expense{
String spender;
double amount;
List<String> consumers;
public Expense(String spender, double amount, String... consumers){
this.spender = spender;
this.amount = amount;
this.consumers = Arrays.asList(consumers);
}
}
static class Owed{
String name;
double amount;
public Owed(String name, double amount){
this.name = name;
this.amount = amount;
}
}
public static void main(String[] args){
List<Expense> expenseList = new ArrayList<>();
expenseList.add(new Expense("Sarah", 400, "Sarah", "John", "Bob", "Alice"));
expenseList.add(new Expense("John", 100, "Bob", "Alice"));
//make list of who owes how much.
Map<String, Double> owes = new HashMap<>();
for(Expense e:expenseList){
double owedAmt = e.amount/e.consumers.size();
for(String c : e.consumers){
if(!e.spender.equals(c)){
if(owes.containsKey(c)){
owes.put(c, owes.get(c) + owedAmt);
}else{
owes.put(c, owedAmt);
}
if(owes.containsKey(e.spender)){
owes.put(e.spender, owes.get(e.spender) + (-1 * owedAmt));
}else{
owes.put(e.spender, (-1 * owedAmt));
}
}
}
}
//make transactions.
// We need to settle all the negatives with positives. Make list of negatives. Order highest owed (i.e. the lowest negative) to least owed amount.
List<Owed> owed = new ArrayList<>();
for(String s : owes.keySet()){
if(owes.get(s) < 0){
owed.add(new Owed(s, owes.get(s)));
}
}
Collections.sort(owed, new Comparator<Owed>() {
#Override
public int compare(Owed o1, Owed o2) {
return Double.compare(o1.amount, o2.amount);
}
});
//take the highest negative, settle it with the best positive match:
// 1. a positive that is equal to the absolute negative amount is the best match.
// 2. the greatest positive value is the next best match.
// todo not sure if this matching strategy gives the least number of transactions.
for(Owed owedPerson: owed){
while(owes.get(owedPerson.name) != 0){
double negAmt = owes.get(owedPerson.name);
//get the best person to settle with
String s = getBestMatch(negAmt, owes);
double posAmt = owes.get(s);
if(posAmt > Math.abs(negAmt)){
owes.put(owedPerson.name, 0.0);
owes.put(s, posAmt - Math.abs(negAmt));
System.out.println(String.format("%s paid %s to %s", s, Double.toString((posAmt - Math.abs(negAmt))), owedPerson.name));
}else{
owes.put(owedPerson.name, -1 * (Math.abs(negAmt) - posAmt));
owes.put(s, 0.0);
System.out.println(String.format("%s paid %s to %s", s, Double.toString(posAmt), owedPerson.name));
}
}
}
}
private static String getBestMatch(double negAmount, Map<String, Double> owes){
String greatestS = null;
double greatestAmt = -1;
for(String s: owes.keySet()){
double amt = owes.get(s);
if(amt > 0){
if(amt == Math.abs(negAmount)){
return s;
}else if(greatestS == null || amt > greatestAmt){
greatestAmt = amt;
greatestS = s;
}
}
}
return greatestS;
}
}
The most optimal solution will require looking ahead to find equal pairs of debtors/creditors to minimize the total number of transactions.
The non-optimized solution is not too hard:
Sum up each person's total credit and debits
Match any two people (it actually does not need to be a debtor and a creditor)
One of these people will owe/be due $A, one will be owe/be due $B
Transfer the lower of |$A| and |$B| so that one person goes to $0
Go back to Step 2 and repeat until all $0
So note you always get one person to zero. With N people, on round (N-1), you will have +$X and -$X (a perfect match) and you set two people to zero. So this is always done in, at most, (N-1) rounds.
To optimize as an 'easy' step, you can always pair up people as soon as you see them That is, someone with +$X and someone with -$X. This alteration should cover >99% of all cases. So you get (N-1) rounds but sometimes (N-2), (N-3), etc if you happen upon a pair of +$X and -$X - you match them up for that round.
However, I have found that a truly optimal solution require (I believe) polynomial-time complexity as you need to run scenarios to try to force pairs.
Example in picture form
Simple solution - pick, say, highest creditor (+$) and lowest debtor (-$). Max of 5 rounds since Round 5 always has a match.
Round 1 Round 2 Round 3 Round 4 Round 5
a $100
b $ 90 $90
c $ 35 $35 $ 35 $ 10
d $ 10 $10 $ 10 $ 10 $ 10
e -$110 -$110 -$ 20 -$ 20 -$ 10
f -$125 -$25 -$ 25
From f e f e e
To a b c c d
Amount $100 $90 $25 $10 $10
Here is us forcing a match by altering Round 1 - we need an automated way of doing this
Round 1 Round 2 Round 3 Round 4
a $100
b $ 90 $ 90 $ 90
c $ 35 $ 35 $ 35 $ 35
d $ 10 $ 10
e -$110 -$ 10
f -$125 -$125 -$125 -$ 35
From e e f f
To a d b c
Amount $100 $10 $90 $35
Another example, showing how in some cases forgoing a "settle up at least one person" round may be an advantage. It doesn't beat the example above, but one could see with enough people, setting up chain reaction of pairs may result in that being optimal.
Round 1 Round 2 Round 3 Round 4
a $ 40 $ 55
b $ 20 $ 5 $ 5
c $ 4 $ 4 $ 4 $ 4
d -$ 4 -$ 4 -$ 4 -$ 4
e -$ 5 -$ 5 -$ 5
f -$ 55 -$ 55
From a f e d
To b a b c
Amount $15 $55 $5 $4
If you take states as nodes of graph then you will be able to use shortest path algorithm to know the answer.

Resources