# 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]]