1

I have a fairly basic math problem in Python & I dont know where to look.

Explanation:

  • I have 100 dollars.
  • I have 10 friends.
  • I want to unevenly distribute my money across my friends.
  • I want to this over & over again using a algorithm.

My only requirements are the following:

  • The smallest amount any friend can receive should be > $5.
  • The biggest amount any friend can receive should be < $40.

from random import randint, shuffle

def divide_number(number, parts_number, allow_zero = False ):

    if (parts_number > number):
        raise ValueError("Number of parts can't be higher than the number");

    parts = {currency: []}
    number_rest = number

    for i in range(1, parts_number + 1):
        if (i == parts_number):
            parts[currency].append(number_rest)
            break
        else:
            new_number = randint(0, number_rest) if allow_zero else randint(1, (number_rest - 
(parts_number - i)) // 2)

        number_rest -= new_number
        parts[currency].append(new_number)

    return parts

Running the function:

divide_number(100, 10)

Output

[2, 37, 8, 10, 2, 4, 4, 4, 26, 3]

This piece of code I've found online seems to work just great but it doesn't meet my requirements. How do I alter it so it does meet my requirements regarding min and max values?

So basicly, I want to go from unfair distribution to fair distribution.

enter image description here

jackygab
  • 58
  • 6
  • Maybe https://stackoverflow.com/questions/18659858/generating-a-list-of-random-numbers-summing-to-1 can be a start point. – ybalcanci Nov 24 '20 at 08:55

2 Answers2

3

Is is not easy to make rather fair distribution with limits. For reasonable sum and parts (p) values we can make p cells (friend pockets) and randomly put coin by coin into them (if possible)

import random

def randparts(summ, p, minn, maxx):
    maxx = maxx - minn
    summ -= p * minn
    if summ < 0:
        return None
    if p * maxx  >=  summ * 2:
        lst = [0] * p
        while summ > 0:
            r = random.randrange(p)
            if lst[r] < maxx:
                summ -= 1
                lst[r] += 1
    else:
        lst = [maxx] * p
        summ = maxx * p - summ
        while summ > 0:
            r = random.randrange(p)
            if lst[r] > 0:
                summ -= 1
                lst[r] -= 1
    for i in range(len(lst)):
        lst[i] += minn
    return lst

print(randparts(100, 10, 5, 40))

>>>[7, 17, 8, 10, 8, 8, 9, 12, 10, 11]

More concise and pythonic version from the comment of Olvin Roght (also removed the second branch for optimization for large summ values)

def randparts(number, divider, min_value, max_value):
    sum_min = divider * min_value
    if sum_min > number:
        return

    number -= sum_min
    result = [min_value] * divider
    while number:
        pocket = randrange(divider)
        if result[pocket] <= max_value:
            result[pocket] += 1
            number -= 1

    return result
MBo
  • 77,366
  • 5
  • 53
  • 86
  • Yes, this code was adapted from version without min limit but it also tries to minimize "false shots" for "large dencity" input values – MBo Nov 24 '20 at 09:53
  • 1
    Perhaps it is not so important for author's purposes. Possible drawback - Poisson or binomial distribution of values (so seldom occurence of large values). – MBo Nov 24 '20 at 09:59
2

Every next iteration you must ensure that you have enough money left to give at least minimum to all friends:

from random import randrange

def divide_number(number, divider, min_value, max_value):
    result = []
    for i in range(divider - 1, -1, -1):
        part = randrange(min_value, min(max_value, number - i * min_value + 1))
        result.append(part)
        number -= part
    return result

Usage:

divide_number(100, 10, 5, 40)

Notice, that friends which took their part first will have much higher chance to get more. You can make it a bit more "fair" by adding shuffle() right before function return:

from random import shuffle

def divide_number(...):
    ...
    shuffle(result)
    return result
Olvin Roght
  • 7,677
  • 2
  • 16
  • 35
  • Thanks. This is exactly what I was looking for. – jackygab Nov 24 '20 at 09:42
  • @jackygab, check also [MBo's answer](https://stackoverflow.com/a/64983418/10824407), his method produces more "fair" result than mine (*if that's what you're looking for*). – Olvin Roght Nov 24 '20 at 09:58