Working with percentage and time convertion with python

Question:

I am working on one automation project using python and selenium. I am about to finish it but I stuck on one point.

I have a time value in min. like 4529 min, 3123.. In my scenario, firstly, I am taking number of project and I am taking percentages of each project. Then I need to assign the time regarding of percentage.

If user has 3 project;
first project: %50
second project: %30
third project: %20

But the problem is when I am trying to divide the total time result should be decimal it can not be float value. So, I need to adjust percentage after taking it from user.

This is what I am trying to do. But the code below it is working correctly sometimes but in some cases it doesnt work properly.

Do you have any suggestion?

existingTime = 4529 
percentage1 = 70
percentage2 = 30

value1 = (existingTime * percentage1)/100
value2 = (existingTime * percentage2)/100

if value1 % 1 != 0:
    mod1 = value1 % 1
    mod2 = value2 % 1
    value1 = int(value1- mod1)
    value2 = value2 + mod1
    if value2 % 1 != 0:
        value2 = math.ceil(value2)
    value2 = int(value2)
    percentage1 = (value1 * 100)/ existingTime
    percentage2 = (value2 * 100)/ existingTime

print(value1)
print(value2)
total = value2+value1

print(total)
print(percentage1)
print(percentage2)

Output:

3170
1360
4530
69.99337602119674 
30.028703908147495
Asked By: Camon

||

Answers:

Let me see if I actually understand your problem.

You have some integer quantity (of minutes), and you want to divide it according to a list of percentages, so that each allocation is an integer which is "as close as possible" to the corresponding percentage and the sum of the allocations is exactly the original quantity.

Is that it?

This is actually a surprisingly subtle problem, usually referred to as the allocation problem, which has been well-studied because it’s essentially the same problem as allocating congressional seats in a proportional-representation electoral system, or proportionally allocating representatives to states in a confederate state like the US; these are problems which require a solution which is not only provably correct, but also visibly fair.

The complication is that there is more than one possible measure for "as close as possible", and different such measures require subtly different algorithms. The difference between the possible results are very slight, usually not more than a difference of one in any single allocation. But in an election, or a distribution of electoral power, even very slight differences can have dramatic consequences. I’m assuming that your problem is not actually that sensitive, and that it’s sufficient that the result be "as close as possible" according to some reasonable criterion, which does not need to be otherwise justified.

So here’s an allocation method which is computationally simple and which produces results which are guaranteed to satisfy the so-called "quota rule", which is that each allocation is either the floor or the ceiling of the exact proportional allocation (that is, the allocation ignoring the need that each allocation be an integer). There are reasons why it’s not very often used in elections (search for the "Alabama paradox" if you’re curious), but it’s simple to explain and simple to verify, which are also useful attributes for an election system.

The algorithm is called "largest remainder", because that’s the way it works. I’ll use your first example, which divides 4529 minutes into three allocations with percentages 50%, 30% and 20%, because dividing into two pieces is just too trivial to show how the algorithm works.

We start by computing the precise allocations, the products of the total allocation with each percentage:

A: 4529 * 50% = 2264.50
B: 4529 * 30% = 1358.70
C: 4529 * 20% =  905.80

Note that since the percentages add up to 1, the sum of the precise allocations is exactly the total allocation.

We start with an initial allocation, using just the integer part of each precise allocation. In mathematical terms, that’s the "floor", but in Python we can compute it using the int constructor, which truncates towards 0. (See help(int)). In most cases, that will not sum to the total allocation, because there will almost always be some fraction in the product. So there will be some unallocated units. But, crucially, there will be fewer unallocated units than the number of allocations, because we’re dropping only remainders, each of which is strictly less than 1.0, and if you sum k such remainders, the sum must be less than k.

So the next step is to figure out how many unallocated items there are:

M = 4529
A + B + C = 2264 + 1358 + 905 = 4527
M - (A + B + C) = 2

So we still need to allocate two units, and we have three possible places to allocate them. As its name suggests, the largest remainder procedure is to sort the partition in descending order by remainder, and then allocate the rest of the units in turn to the partitions with the largest remainders. In this case, the largest remainder is 0.80 (C) and the second-largest remainder is 0.70 (B), so that’s where the two units go, resulting in the allocation:

A: 2264     = 2264 (49.989%)
B: 1358 + 1 = 1359 (30.007%)
C:  905 + 1 =  906 (20.004%)
              ----
Total         4529

As a python program:

def allocate(m:int, w:[float]):
    """ Given a number of units m, allocate them according to
        the vector of weights w.
    """
    # Normalise w
    wsum = sum(w)
    # compute precise allocation for each value
    precise = [m * v / wsum for v in w]
    # and the initial allocation
    alloc = [int(v) for v in precise]
    # compute the shortfall
    needed = m - sum(alloc)
    if needed: # Almost always true, but just in case
        # make a list of (index, initial, remainder) sorted by remainder
        triples = sorted(((i, a, p - a)
                          for (i, a), p in zip(enumerate(alloc), precise)),
                         key = lambda v:v[2])
        # increment the allocation for the 'needed' largest elements
        triples = (triples[:-needed] 
                   + [(i, a+1, r) for i, a, r in triples[-needed:]])
        # sort again by the index, in order to get the triples back
        # into the right order, and then extract the allocs
        alloc = [t[1] for t in sorted(triples)]
    return alloc
Answered By: rici
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.