From 6d0908f7728c33bea947769e7d057b095fb8175c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Dachary?= Date: Tue, 16 Aug 2022 09:02:19 +1000 Subject: [PATCH 1/2] .gitignore emacs backups --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ea765f8..30851d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ venv *.csv +*~ -- 2.40.1 From 332efe15d057510dcb0dcb95a10d6df783838758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Dachary?= Date: Tue, 16 Aug 2022 01:40:38 +1000 Subject: [PATCH 2/2] implementation of the revenue sharing model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Loïc Dachary --- share.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 share.py diff --git a/share.py b/share.py new file mode 100644 index 0000000..7ead861 --- /dev/null +++ b/share.py @@ -0,0 +1,91 @@ +# py.test-3 --log-cli-level=DEBUG -v -k test_share_income share.py + +EXPENSE = 1 + +# +# For a given income, return a list of the members who will not be paid in full +# and how much will remain in their expense balance. +# +# See https://forum.hostea.org/t/decision-revenue-sharing-model/92 +# +# The members list given in argument is a list of pairs of +# +# ['member', EXPENSE] +# +# Where 'member' is the unique id of the member and EXPENSE is a +# positive integer. +# +# The income argument must be an integer lower or equal to the sum of +# all members EXPENSE. +# +# The returned member list is a subset of the members list given in +# argument and only contains the members that cannot be paid in full +# with the income given in argument. For instance: +# +# share_income(3, [['a', 2], ['b', 2]]) == [['b', 1]] +# +# Means that with an income of 3, only member 'a' can be paid in full and +# the remaining expense for member 'b' is 1. +# +def share_income(income, members): + if income <= 0: + # + # Recursion termination: no more income to share, none of the + # remaining members get anything + # + return members + if income <= len(members): + # + # The income is an integer number: when it cannot be divided + # among members (2/3 == 0), some of them get 1 and the others + # get nothing. If the income is consistently very low some + # members to never get paid but the amount of money involved + # would then be so low that no member would care. + # + share = 1 + count = income + else: + # + # The share that each member will get at this stage of the + # recursion is the lowest expense. If there is not enough + # income to give each member the lowest expense, it is divided + # evenly. + # + share = min(int(income / len(members)), min(members, key=lambda m: m[EXPENSE])[EXPENSE]) + count = len(members) + # + # remaining is the list of members that cannot be paid in full + # with the share. + # + remaining = [] + for i in range(count): + m = members[i] + m[EXPENSE] -= share + if m[EXPENSE] > 0: + # + # If the member needs more to be paid in full, they are + # elligible to participate in the next recursion round. + # + remaining.append(m) + # + # This is the border case when income <= len(members): some + # members cannot get their share because there is not enough + # income to provide an equal share of 1 to everyone and were + # excluded from the loop above. They are added because they could + # not be paid in full. + # + remaining.extend(members[count:]) + # + # Now that each member got an equal share (except for the border + # case above) recurse, but only with the members that expect to be + # paid more. + # + return share_income(income - share * count, remaining) + +def test_share_income(): + assert share_income(1, [['a', 1], ['b', 1]]) == [['b', 1]] + assert share_income(5, [['a', 10], ['b', 2]]) == [['a', 7]] + assert share_income(5, [['a', 2], ['b', 10], ['c', 1]]) == [['b', 8]] + assert share_income(5, [['a', 2], ['b', 10], ['c', 1], ['d', 40]]) == [['b', 9], ['d', 39]] + assert share_income(5, [['a', 2], ['b', 10], ['c', 1], ['d', 40], ['e', 3]]) == [['a', 1], ['b', 9], ['d', 39], ['e', 2]] + assert share_income(5, [['a', 2], ['b', 10], ['c', 1], ['d', 40], ['e', 3], ['f', 1]]) == [['a', 1], ['b', 9], ['d', 39], ['e', 2], ['f', 1]] -- 2.40.1