A Ranking algorithm - algorithm

I need to sort some products base on user ratings.
Suppose we have 3 products {a,b,c} and we have user's feedbacks about this products.
It's not important which user give us feedback (this question is not about correlative filtering if you are familiar with it - user interests is not the case here)
Each of these below lines is feedback from users when they tried to compare the 3 products:
a 150 points - b 0 points (this user just told us what he thinks of 2 products a and b and in comparison of a and b he thought that if he gives a 150 point then b worth 0 points)
a 150 points - c 20 points
c 200 points - a 10 points (despite the previous one this user thinks that c is better that a)
a 200 points - b 40 points - c 100 points
a 150 points - b 50 points
a 150 points - b 20 points
(These ratings are just a sample and in the real world number of products and ratings are much bigger than this)
Now I need an algorithm to find product's rankings based on user votes. In my point of view, the best way is to describe this problem with a correlation graph and connect all the products to each other.
Any kind of help or tips is appreciated.
/********************************************************************************/
You cannot just add the points and calculate the mean of product's points because it's important how it got his points. Suppose a has got 800 points against b - then c get 10 points against a like this:
a 200 - b 0
a 200 - b 0
a 200 - b 0
a 200 - b 0
c 10 - a 0 (this means that c is better than a)
so definitely a is better than b but with a small 10 points c got a better rank from a
/********************************************************************************/

You have some challenges. Add a ranking c 0 - b 20 and you got a circle, where c < b < a < c.
And of course your order is not only not transitif ( from a < b < c does not follow a < c ), it is also not total (there might be elements you cannot decide which is better because no user voting has been done, even through other elements.
What you get is a disconnected, directed, finite graph. (use the direction of the edges to say which element(node is better).
Starting at a certain node you can find better nodes marching through the graph, maybe finding multiple non comparable solutions. If you visit the starting node again, stop processing that path.
Maybe order theory in math can help you: look for order theory, partial order, Hasse diagram.
To make this more practical:
Use a two dimensional array with a row and a column per element. In the cell(a,b) calculate the sum of the ratings. Starting at a certain element a, follow all positiv (>0) connections, until you either reach a node that has no positiv connections or come back to a node you visited already. These nodes are your solutions.

A baysean rating system might be your best bet - it takes into account the votes and the relative number of votes an item has in order to give it a weighted score.

I think you need to relate how each person voted on each product - for instance:
person 1 voted: 100 for a, 50 for b and 0 for c
person 2 voted 0 for a, 200 for b and 80 for c
this should be translated into:
person 1 voted 3 for a, 2 for b and -1 for c
person 2 voted -1 for a, 3 for b and 2 for c
where I'm using:
3 for the highest vote
2 for the second highest
1 for the lowest
AND -1 if they voted 0 (indication that they disliked/did-not-consider the product)
my initial thought on it anyway

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 for sequential reward points

I want to write an algorithm to find the sequential reward points.
The inviter gets (1/2)^k points for each confirmed invitation, where k is the level of the invitation: level 0
(people directly invited) yields 1 point, level 1 (people invited by someone invited by the original customer)
gives 1/2 points, level 2 invitations (people invited by someone on level 1) awards 1/4 points and so on.
Only the first invitation counts: multiple invites sent to the same person don't produce any further points,
even if they come from different inviters and only the first invitation counts.
For instance:
Input:
A recommends B
B accepts
B recommends C
C accepts
C recommends D
B recommends D
D accepts
would calculate as:
A receives 1 Point from the recommendation of B, 0.5 Point from the recommendation of C by B and
another 0.25 Point by the recommendation of D by C. A gets a total score of 1.75 Points.
B receives 1 Point from the recommendation of C and 0.5 Point from the recommendation of D by C. B receives no Points from the recommendation of D because D was invited by C before. B gets a total score of 1.5 Points.
C receives 1 Point from the recommendation of D. C gets a total score of 1 Point.
Output:
{ “A”: 1.75, “B”: 1.5, “C”: 1 }
What should be the algorithm for that? I think Dynamic Programing has to be use here.
This is simply an ancestors search in a tree. By keeping track of the depth, you know how many points to award.
Pseudocode
def add_points(accepter):
depth = 0
while accepter has an inviter:
accepter.inviter.points += (0.5)^depth
accepter = accepter.inviter
depth += 1
This algorithm is O(number of parents) and since you need to traverse all parents to update, you know you can't do any better complexity-wise.

Optimization - distribute participants far from each other

This is my first question. I tried to find an answer for 2 days but I couldn't find what I was looking for.
Question: How can I minimize the amount of matches between students from the same school
I have a very practical case, I need to arrange a competition (tournament bracket)
but some of the participants might come from the same school.
Those from the same school should be put as far as possible from each other
for example: {A A A B B C} => {A B}, {A C}, {A B}
if there are more than half participants from one school, then there would be no other way but to pair up 2 guys from the same school.
for example: {A A A A B C} => {A B}, {A C}, {A A}
I don't expect to get code, just some keywords or some pseudo code on what you think would be a way of making this would be of great help!
I tried digging into constraint resolution algorithms and tournament bracket algorithms, but they don't consider minimising the amount of matches between students from same school.
Well, thank you so much in advance!
A simple algorithm (EDIT 2)
From the comments below: you have a single elimination tournament. You must choose the places of the players in the tournament bracket. If you look at your bracket, you see: players, but also pairs of players (players that play the match 1 against each other), pairs of pairs of players (winner of pair 1 against winner of pair 2 for the match 2), and so on.
The idea
Sort the students by school, the schools with the more students before the ones with the less students. e.g A B B B B C C -> B B B B C C A.
Distribute the students in two groups A and B as in a war card game: 1st student in A, 2nd student in B, 3rd student in A, 4th student in B, ...
Continue with groups A and B.
You have a recursion: the position of a player in the level k-1 (k=n-1 to 0) is ((pos at level k) % 2) * 2^k + (pos at level k) // 2 (every even goes to the left, every odd goes to the right)
Python code
Sort array by number of schools:
assert 2**math.log2(len(players)) == len(players) # n is the number of rounds
c = collections.Counter([p.school for p in players])
players_sorted_by_school_count = sorted(players, key=lambda p:-c[p.school])
Find the final position of every player:
players_sorted_for_tournament = [-1] * 2**n
for j, player in enumerate(players_sorted_by_school_count):
pos = 0
for e in range(n-1,-1,-1):
if j % 2 == 1:
pos += 2**e # to the right
j = j // 2
players_sorted_for_tournament[pos] = player
This should give groups that are diverse enough, but I'm not sure whether it's optimal or not. Waiting for comments.
First version: how to make pairs from students of different schools
Just put the students from a same school into a stack. You have as many stack as schools. Now, sort your stacks by number of students. In your first example {A A A B B C}, you get:
A
A B
A B C
Now, take the two top elements from the two first stacks. The stack sizes have changed: if needed, reorder the stacks and continue. When you have only one stack, make pairs from this stack.
The idea is to keep as many "schools-stacks" as possible as long as possible: you spare the students of small stacks until you have no choice but to take them.
Steps with your second example, {A A A A B C}:
A
A
A
A B C => output A, B
A
A
A C => output A, C
A
A => output A A
It's a matching problem (EDIT 1)
I elaborate on the comments below. You have a single elimination tournament. You must choose the places of the players in the tournament bracket. If you look at your bracket, you see: players, but also pairs of players (players that play the match 1 against each other), pairs of pairs of players (winner of pair 1 against winner of pair 2 for the match 2), and so on.
Your solution is to start with the set of all players and split it into two sets that are as diverse a possible. "Diverse" means here: the maximum number of different schools. To do so, you check all possible combinations of elements that split the set into two subsets of equals size. Then you perform recursively the same operation on those sets, until you arrive to the player level.
Another idea is to start with players and try to make pairs with other players from other school. Let's define a distance: 1 if two players are in the same school, 0 if they are in a different school. You want to make pairs with the minimum global distance.
This distance may be generalized for the pairs of players: take the number of common schools. That is: A B A B -> 2 (A & B), A B A C -> 1 (A), A B C D -> 0. You can imagine the distance between two sets (players, pairs, pairs of pairs, ...): the number of common schools. Now you can see this as a graph whose vertices are the sets (players, pairs, pairs of pairs, ...) and whose edges connect every pair of vertices with a weight that is the distance defined above. You are looking for a perfect matching (all vertices are matched) with a minimum weight.
The blossom algorithm or some of its variants seems to fit your needs, but it's probably overkill if the number of players is limited.
Create a two-dimensional array, where the first dimension will be for each school and the second dimension will be for each participant in this take-off.
Load them and you'll have everything you need linearly.
For example:
School 1 ------- Schol 2 -------- School 3
A ------------ B ------------- C
A ------------ B ------------- C
A ------------ B ------------- C
A ------------ B
A ------------ B
A
A
In the example above, we will have 3 schools (first dimension), with school 1 having 7 participants (second dimension), school 2 having 5 participants and school 3 having 3 participants.
You can also create a second array containing the resulting combinations and, for each chosen pair, delete this pair from the initial array in a loop until it is completely empty and the result array is completely full.
I think the algorithm in this answer could help.
Basically: group the students by school, and use the error tracking idea behind Bresenham's Algorithm to distribute the schools as far apart as possible. Then you pull out pairs from the list.

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.

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