Split number into chunks according to rank - python

A very practical task. I have a number of items that need to be distributed into several stores, according to store rank. So the higher the rank, the more items store will get, so store with rank 1 will get most items and store with rank 100 least.
So the inputs are:
number of items to distribute
stores, represented by their rank, can have duplicates as well, and does not follow any pattern, for instance [1, 4, 40]
What I have right now is the following:
I inverse the ranks, so that smaller number would become biggest (sum(n...) - n)
I take percentage of each store: inversed_rank \ sum(inversed_ranks)
Multiply the amount I need to distribute by the percentage
It works fine on some of the numbers, like:
[1, 4, 40]
1 = 147.2
4 = 137.1
40 = 16.7
Clearly the store with rank 40 gets the least, but with more stores it flattens:
[6,3,24,10, 25, 12, 14,35,40, 16,28,29,17,1,26,23,8,10]
1 = 17.7
3 = 17.5
6 = 17.4
8 = 17.3
10 = 17.2
10 = 17.2
12 = 17.1
14 = 16.9
16 = 16.8
17 = 16.8
23 = 16.5
24 = 16.4
25 = 16.4
26 = 16.3
28 = 16.2
29 = 16.1
35 = 15.8
40 = 15.5
So store with rank 40 gets only a fraction less than the top.
Does anyone have an idea how to make it a bit more "curved"?
My Python code:
number = 301
ranks = [6,3,24,10, 25, 12, 14,35,40, 16,28,29,17,1,26,23,8,10]
#ranks = [1,4,40]
ranks.sort()
total = sum(ranks)
tmp = 0
reversed = []
for value in ranks:
reversed.append(total - value)
total = sum(reversed)
print('Items per rank:')
for i in range(len(ranks)):
percent = reversed[i] / total
print(str(ranks[i]) + ' = ' + "{:10.1f}".format(percent * number))
tmp = tmp + percent * number
print('')
print('Total = ' + str(tmp))
You may play with it here: https://trinket.io/python/a15c54b978
Would be perfect to have a more math rather than library solution as I will need to translate it into Excel VBA
Solution
Í have also added a factor to control the distribution:
number = 301
ranks = [6,3,24,10, 25, 12, 14,35,40, 16,28,29,17,1,26,23,8,10]
#ranks = [1,4,40]
factor = 1
ranks.sort()
total = sum(ranks)
tmp = 0
max = max(ranks) + 1
reversed = []
for value in ranks:
reversed_value = max - value
reversed_value = pow(reversed_value, factor)
reversed.append(reversed_value)
total = sum(reversed)
print('Items per rank:')
for i in range(len(ranks)):
percent = reversed[i] / total
value = percent * number
print(str(ranks[i]) + ' = ' + "{:10.1f}".format(value))
tmp = tmp + value
print('')
print('Total = ' + str(tmp))

Does this work ?
rank= [6,3,24,10, 25, 12, 14,35,40, 16,28,29,17,1,26,23,8,10]
rank_= [6,3,24,10, 25, 12, 14,35,40, 16,28,29,17,1,26,23,8,10]
number = 301
# assuming the point that is the most distant gets the value 0
for i, p in enumerate(rank):
rank[i] = p*-1 + max(rank) + 1
sum = sum(rank)
for i, p in enumerate(rank):
val = p / sum
val *= number
rank[i] = val
for i in range(len(rank)):
print(rank_[i], ": ", rank[i], end="\n\n")
output:
6: 26.806615776081426
3: 29.104325699745548
24: 13.020356234096692
10: 23.74300254452926
25: 12.254452926208652
12: 22.211195928753177
14: 20.6793893129771
35: 4.595419847328244
40: 0.7659033078880407
16: 17.615776081424936
28: 8.424936386768447
29: 7.659033078880407
17: 16.849872773536894
1: 29.104325699745548
26: 9.956743002544528
23: 12.254452926208652
8: 23.74300254452926
10: 22.211195928753177

Related

How to get multiples of 100 rounded to the nearest thousand

I'm learning Python and I'm trying to come up with a for loop (or any other method) that can return multiples of 100 but rounded to the nearest thousand, here's what I have right now:
huneds = [h * 100 for h in range(1,50)]
for r in huneds:
if r % 3 == float:
print(r)
else:
break
The built-in round() function will accept a negative number that you can use to round to thousands:
for r in huneds:
print(round(r, -3))
Which prints:
0
0
0
0
0
1000
1000
1000
1000
1000
1000
1000
1000
1000
2000
...
4000
4000
5000
5000
5000
5000
You can see use
for n in range(0,2500,100):
print(n, ' -> ',1000 * round(n / 1000))
For any number m, m is a multiple of n if the remainder of n / m is 0. I.e. n % m == 0 or in your case; r % 100 == 0, as the modulus operator (%) returns the remainder of a division. Use:
for r in huneds:
if r % 100 == 0:
print(r)
But every number is already a multiple of 100, as you multiplied all of them by 100.
You may be after something like:
# Range uses (start, stop, step) params
# print(list(range(0, 200, 10))) --> [0, 10, 20, ... 170, 180, 190]
for r in range(0, 200, 10):
if r % 100 == 0 and r != 0:
print(r)
Outputs
100
But you would like to round to the nearest 1000. The round() function can do that.
for r in range(0, 2000, 10):
if r % 100 == 0 and r != 0:
print(f"{r} | {round(r, -3)}")
100 | 0
200 | 0
300 | 0
400 | 0
500 | 0
600 | 1000
700 | 1000
800 | 1000
...
The f string does the same as r + ' | ' + round(r, -3)
This shows the number that is a multiple of 100 which is r, and then it rounded to the nearest 1000
round()'s second argument is the amount of digits to round too, as we are going to the nearest 1000, we use -3 as you are going on the left side of the decimal
I suggest having a read of:
https://www.w3schools.com/python/ref_func_range.asp
And I highly reccomend: this site (python principles) for learning python. Pro membership is currently free
Simply do this,
list(map(lambda x: round(x/1000) * 1000, huneds))
It'll return you a list of rounded values for all the items of the list huneds.

Python - Adding count to output

I'm writing a simple program that takes a number and continually doubles it until it has reached an upper limit. The code I've written does this with a for loop and while loop, but I want to add a count to the output to see how many iterations through the while loop it took to get to the upper limit.
The code looks like this:
def double_function():
print('Enter an upper range to target')
upper_range = int(input())
for number in range(0, upper_range):
print('Enter a number to double')
number = float(input())
while number < upper_range:
number * 2
number += number
print(number)
else:
break
output_list = list(str(number))
iterations = enumerate(output_list)
print('It took ' + str(iterations) + ' iterations to reach ' + str(number))
double_function()
If upper_range = 1000 and number = 1, output is:
2.0
4.0
8.0
16.0
32.0
64.0
128.0
256.0
512.0
1024.0
It took <enumerate object at 0x7ffe02f74e40> iterations to reach 1024.0
I tried using enumerate because that's the only suggestion I've seen, but every other example used it with lists. I tried converting my output to a list, but I'm still not getting the output I want. I want it to look something like this:
1: 2.0
2: 4.0
3: 8.0
4: 16.0
5: 32.0
6: 64.0
7: 128.0
8: 256.0
9: 512.0
10: 1024.0
It took 10 iterations to reach 1000
Thanks for the help
enumerate returns an enumerate object see help(enumerate).
To get what you're looking for, just use len(output_list)
Just realized your "output_list" isn't actually a list containing the intermediate results. So to actually get the number of iterations, you can just initialize a counter before starting the while loop, then increment it by one in the while-loop block, and once it breaks, that variable will store the number of iterations performed.
RESOLVED. Initialized a counter before starting while loop and incremented by one inside the loop. Code looks like this:
def double_function():
print('Enter an upper range to target')
upper_range = int(input())
for number in range(0, upper_range):
print('Enter a number to double')
number = float(input())
iterations = 0
while number < upper_range:
number * 2
number += number
iterations += 1
print(iterations, number)
else:
break
print('It took ' + str(iterations) + ' iterations to reach ' + str(number))
double_function()
Output if upper_range = 1000, number = 1:
1 2.0
2 4.0
3 8.0
4 16.0
5 32.0
6 64.0
7 128.0
8 256.0
9 512.0
10 1024.0
It took 10 iterations to reach 1024.0

display if the numbers are consecutive for upto 3 positions exactly in pandas

I have a df "Index_label", which has only one column(name: column1) which contain numbers.
if we have consecutive position ==3. i would want it as out put
column1
25
26------------consecutive upto 2 positions
110
111
112
113---------------------consecutive upto 4 positions
455
456
457---------------------consecutive upto 3 positions
desired o/p:
110
111
112
455
456
457
You can use this code (assuming you get the numbers as an iterable of some kind):
def getConsecutive(numbers):
consecutive = [] # all 3-consecutive numbers
sub_series = [] # current sub-series of consecutive numbers
for number in numbers:
if len(sub_series) == 0: # if the current sub-series is empty
sub_series.append(number) # add the current number to it
else:
prev = sub_series[-1]
if number != prev+1: # if the current number is not consecutive
if len(sub_series) >=3: # if there are at least 3 numbers in the sub_series
consecutive.extend(sub_series[0:3]) # add the first 3 of the sub-series
sub_series.clear() # clear the sub-series
sub_series.append(number) # add the current number
if len(sub_series) >=3: # check if a 3-consecutive sub series is found
consecutive.extend(sub_series[0:3]) # add it in
return consecutive
numbers1 = [25,26,110,111,112,113,455,456,457]
numbers2 = [110, 111, 112, 113, 114, 115, 116, 117]
print(getConsecutive(numbers1)) # output = [110, 111, 112, 455, 456, 457]
print(getConsecutive(numbers2)) # output = [110, 111, 112]
This works (I think?), using a while loop and jumping up the count when a consecutive series is found:
def three_in_a_row(array):
output=[]
c = 0
while c < len(array)-2:
if (array[c] + 1 == array[c+1]) and (array[c] + 2 == array[c+2]):
output += [array[c],array[c+1],array[c+2]]
d = 1
while (array[c] + d == array[c+d]):
d+=1
if (d+c) >= len(array):
break
c += d
else:
c+=1
return output
print(three_in_a_row([1,2,3,4,5,6,7,8,9]))
print(three_in_a_row([1,2,3,7,8,9]))
print(three_in_a_row([1,8,9,4,2,4,5,6,7,8]))
Output:
[1, 2, 3]
[1, 2, 3, 7, 8, 9]
[4, 5, 6]
Fun exercise!

Combinations of Combinations

There are nearly 14 million combinations from a selection of 6 numbers from a range of 1-49. From the 14 million, I've cut the combinations down to 8.9 million by selecting only those where the sum of the 6 number combination must equate to between 120 and 180.
Example: 5, 10, 20, 27, 29, 40 = 131
Of the remaining 8.9 million combinations, I'm trying to remove all combinations that contain less than 2 and more than 4 odd numbers.
Basically, I want Python to show me how many combinations of those 8.9 million combinations have between 2-4 odd numbers in their combinations. All combinations of only 1 or less odd numbers and 5 or more odd numbers would be excluded from the results.
Example: 5, 10, 20, 27, 32, 40 = 2 odd numbers (it would be included in the amount of combinations).
Thank you!
import functools
_MIN_SUM = 120
_MAX_SUM = 180
_MIN_NUM = 1
_MAX_NUM = 49
_NUM_CHOICES = 6
#functools.lru_cache(maxsize=None)
def f(n, l, s):
assert(all(isinstance(v, int) and v >= 0 for v in (n, l, s)))
return 0 if s > _MAX_SUM else (
int(s >= _MIN_SUM) if n == 0 else (
sum(f(n-1, i+1, s+i) for i in range(l, _MAX_NUM+1))
)
)
result = f(_NUM_CHOICES, _MIN_NUM, 0)
print('Number of choices = {}'.format(result))
You can use the combinations() function from itertools and just brutally count the combinations that are eligible:
from itertools import combinations
eligible = 0
for combo in combinations(range(1,50),6):
total = sum(combo)
if total < 120 or total > 180:
continue
odds = sum(n&1 for n in combo)
if odds < 2 or odds > 4:
continue
eligible += 1
print(eligible) # 7221936
It only takes a few seconds (10-12)
You can do almost exactly the same as you are currently doing. Just add a parameter that counts how many odd numbers there are and increase it when you add an odd. Then you can adjust your tests accordingly:
import functools
_MIN_SUM = 120
_MAX_SUM = 180
_MIN_NUM = 1
_MAX_NUM = 49
_NUM_CHOICES = 6
_MIN_ODDS = 2
_MAX_ODDS = 4
#functools.lru_cache(maxsize=None)
def f(n, l, s = 0, odds = 0):
if s > _MAX_SUM or odds > _MAX_ODDS:
return 0
if n == 0 :
return int(s >= _MIN_SUM and odds >= _MIN_ODDS)
return sum(f(n-1, i+1, s+i, odds + i % 2) for i in range(l, _MAX_NUM+1))
result = f(_NUM_CHOICES, _MIN_NUM)
print('Number of choices = {}'.format(result))
Because it's memoized and prunes branches, this runs quickly:
150 ns ± 13 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Running it with the more managable:
_MIN_SUM = 1
_MAX_SUM = 8
_MIN_NUM = 1
_MAX_NUM = 8
_NUM_CHOICES = 2
_MIN_ODDS = 2
_MAX_ODDS = 4
returns 4 which corresponds to the set:
(1, 3),
(1, 5),
(1, 7),
(3, 5)

get value from data that depend on other data

i am trying to get the l input which will be between 0,1. the l input will be for A column. and second input will be the 'mesafe' column so the result must be 23 which is for A and mesafe's zero column. I get some error.
import pandas as pd
import numpy as np
def var():
df = pd.read_csv('l_y.txt')
l=float(input("speed of the wind:"))
w=int(input("range:"))
for l in range(0, 1) and w in range(0, 100) :
print(df['A'].values[0])
l_y.txt=( mesafe A B C D E F
0 100 23 18 14 8 4 0
1 1000 210 170 110 60 40 30
2 5000 820 510 380 300 230 160
3 10000 1600 1200 820 560 400 250
4 20000 2800 2100 1600 1000 820 500
5 50000 5900 4600 3400 3200 1600 1100
6 100000 10000 8100 6100 3900 2800 2000 )
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
var()
File "C:\Users\user\AppData\Local\Programs\Python\Python36-32\ml.py", line
8, in var
for l in range(0, 1) and w in range(0, 100) :
TypeError: 'bool' object is not iterable
You have to follow the format of the language. In this case if you want to run a double loop with l and w:
for l in range(0, 1):
for w in range(0, 100) :
print(df['A'].values[0])
Note the indent, if you don't indent properly then the python codes won't work correctly.
Also your code here doesn't seem to accomplish anything except printing the same thing for 200 times. What are you trying to do?
Ok, I assume you want to get a certain value from your matrix, depending on two input values l and w, so that if l is between 0 and 1 column 'A' should be selected. (I further assume, that if l is between 1 nd 2 it's column 'B', 2 <= l < 3 -> 'c', and so on...)
The row is directly derived from w with the data in the mesafe-column: if w is between 0 and 100 -> row 0, between 100 and 1000 -> row 1 and so on...
Well, this can be achieved as follows:
l = .3 # let's say user types 0.3
There is some mapping between l and the letters:
l_mapping = [1, 5, 12, 20, 35, 50] # These are the thresholds between the columns A...F
l_index = np.searchsorted(l_mapping, l) # calculate the index of the column letter
col = df.columns[1:][l_index] # this translates l to the column names from A...F
col # look what col is when l was < 1
Out: 'A'
w = 42 # now the user Input for w is 42
row = np.searchsorted(df.mesafe.values, w) # this calculates the fractional index of w in df.mesafe
row
Out: 0
So with these two formulas you get column and row Information to index your desired result:
df[col].iloc[row]
Out: 23
Summing this all up in a function would look like this:
def get_l_y(l, w, df_ly):
l_mapping = [1, 5, 12, 20, 35, 50]
l_index = np.searchsorted(l_mapping, l)
col = df_ly.columns[1:][l_index]
row = np.searchsorted(df.mesafe.values, w)
print(l, w, col, row) # print Input and calculated row- and column- values for testing purpose, can be deleted/commented out if everything works as you want
return df[col].iloc[row]
This function expects l, w and the pandas-dataframe of your matrix as input Parameters and returns l_y.

Categories

Resources