Crates.io | pagat |
lib.rs | pagat |
version | 0.0.2 |
source | src |
created_at | 2022-10-24 10:47:32.770848 |
updated_at | 2023-04-16 18:09:46.272559 |
description | A library that helps you split the bill |
homepage | |
repository | https://github.com/macabu/pagat.git |
max_upload_size | |
id | 695772 |
size | 45,286 |
A library that helps you split the bill; made for learning purposes, not production-ready.
[dependencies]
pagat = "0.0.2"
This crate has the following concepts:
Person
: someone who participates in the bill splitting;Money
: i32 for money calculations, using 2 decimals for cents (such that 100 = $1.00)Payment
: payment made by someone that can involves up to N amount of people
D
, so you can record this payment to B
and C
onlyObligation
: the record that says someone has to pay someone else a certain amount of money
Please refer to the tests in order to see different use cases.
It uses directed graphs to represent who needs to pay whom how much. However, such representation is not well optimized and would require many people to pay others and keep track of the money, which defeats the purpose of the library.
The Solver optimizes the connections in such a way that you have the minimum amount of payments being made, optimistically we can have N-1 payments where N is the amount of people involved.
Implementation-wise, there are four passes the solver executes.
Reduces doubly connected edges to a single edge connection. The resulting direction is dictated by subtracting the edges' weights. In case the result is zero, then both edges are removed.
Consider the following example, where A
has to pay B
$10, and B
has to pay A
$20:
┌─────┐ $10 ┌─────┐
│ ├──────────────►│ │
│ A │ │ B │
│ │◄──────────────┤ │
└─────┘ $20 └─────┘
After the first pass, we would have only B
paying A
$10
┌─────┐ ┌─────┐
│ │ │ │
│ A │ │ B │
│ │◄──────────────┤ │
└─────┘ $10 └─────┘
In the case where A
and B
would pay the same, then both connections are removed.
After the first pass, we are guaranteed to not have doubly connected edges, and we can move on to the second pass.
By taking and an edge, we check if the target of that edge has an edge to yet another node that our source edge has as well. If we find it, we remove the first edge, add update the weights of the remaining edges (what a mouthful)
Let's see an example:
┌───┐ ┌───┐
│ │ $25 │ │
│ H ├──────►│ C │
│ │ │ │
└─┬─┘ └─┬─┘
│ │
$50 │ ┌───┐ │ $50
│ │ │ │
└──►│ A │◄──┘
│ │
└───┘
The steps are the following:
H->C
$25
) to H->A
$25
) from H->C
With that the resulting graph is:
┌───┐ ┌───┐
│ │ │ │
│ H │ │ C │
│ │ │ │
└─┬─┘ └─┬─┘
│ │
$75 │ ┌───┐ │ $25
│ │ │ │
└──►│ A │◄──┘
│ │
└───┘
The reasoning behind this optimization is simple: H
doesn't need to pay C
, if both H
and C
pay to the same person A
.
In case the subtraction step is negative, we simply invert the direction of the edge.
If A
is paying B
$10, and B
is paying C
that same amount, we can reduce it so that A
pays C
directly the $10.
More technically speaking, if there's an edge A --[X]--> B and another B --[X]--> C, it can be reduced to A --[X]--> C.
To make things a bit faster, we don't actually remove any connections, but set their weight to 0
.
It is in this step where we do the removal, after all other three steps.