From 002e867bc4bb494501da3612e37a60126768f5fb Mon Sep 17 00:00:00 2001 From: Laszlo Makk Date: Fri, 2 Aug 2024 14:58:46 +0000 Subject: [PATCH] Make recalculation stable across repayments in suggested reimbursements (#179) * suggested reimbursements: make recalculation stable across repayments Previously, after a group participant executed a suggested reimbursement, rerunning getSuggestedReimbursements() could return a completely new list of suggestions. With this change, getSuggestedReimbursements() should now be stable: if it returns a graph with n edges, and then a repayment is made according to one of those edges, when called again, it should now return the same graph but with that one edge removed. The trick is that the main logic in getSuggestedReimbursements() does not rely on balancesArray being sorted based on .total values, only that the array gets partitioned into participants with credit first and then participants with debt last. After a repayment is made, re-sorting based on .total values would result in a new order hence new suggestions, but sorting based on usernames/participantIds should be unaffected. fixes https://github.com/spliit-app/spliit/issues/178 * Prettier --------- Co-authored-by: Sebastien Castiel --- src/lib/balances.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib/balances.ts b/src/lib/balances.ts index 74eae78..70fea3f 100644 --- a/src/lib/balances.ts +++ b/src/lib/balances.ts @@ -82,13 +82,29 @@ export function getPublicBalances(reimbursements: Reimbursement[]): Balances { return balances } +/** + * A comparator that is stable across reimbursements. + * This ensures that a participant executing a suggested reimbursement + * does not result in completely new repayment suggestions. + */ +function compareBalancesForReimbursements(b1: any, b2: any): number { + // positive balances come before negative balances + if (b1.total > 0 && 0 > b2.total) { + return -1 + } else if (b2.total > 0 && 0 > b1.total) { + return 1 + } + // if signs match, sort based on userid + return b1.participantId < b2.participantId ? -1 : 1 +} + export function getSuggestedReimbursements( balances: Balances, ): Reimbursement[] { const balancesArray = Object.entries(balances) .map(([participantId, { total }]) => ({ participantId, total })) .filter((b) => b.total !== 0) - balancesArray.sort((b1, b2) => b2.total - b1.total) + balancesArray.sort(compareBalancesForReimbursements) const reimbursements: Reimbursement[] = [] while (balancesArray.length > 1) { const first = balancesArray[0]