code:
import random
import numpy as np
X=10
Y=5
while True:
random_value=np.random.choice((range(1,X)),Y)
if sum(random_value)==X:
break
else:
continue
Question
This code works fine for small value of X and Y but it does not work for big values like X=56 and Y= 52. My aim is to generate random values from X in a list whose size is Y and also the sum of the random value should b X
How should i optimise the code to get output for big values
CodePudding user response:
You can sample from a suitable multinomial distribution, but the given range [1..limit-1]
will usually not be exhausted in the result, even if it were possible.
import numpy as np
rng = np.random.default_rng()
N = 52
limit = 56
a = rng.multinomial(limit - N, np.full(N, 1.0/N)) 1
print(a)
print(f'sum:{a.sum()}, length:{a.shape[0]}')
Output (random)
[1 2 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1]
sum:56, length:52
CodePudding user response:
Here's some working code. In a case like X = 56
and Y = 52
, most of the numbers will be ones, so this code figures out how many 1s are needed, generates the other numbers, and then subtracts from or adds to those random numbers until they sum to the needed sum.
import numpy as np
import random
def get_vals_from_X_Y(X, Y):
# If X = 56 and Y = 55, can have a single 2 in generated values and then 54 ones.
# If X = 56 and Y = 54, can have 2 twos and 52 ones.
# Pattern is that you need (Y - (X - Y)) ones, which simplifies to (2Y - X).
# This pattern ONLY APPLIES if Y >= X / 2. If Y < X / 2, then every Y value needs to be >= 2.
if Y >= X / 2:
num_1s_needed = 2*Y - X
else:
num_1s_needed = 0
# vals will eventually contain Y values summing to X.
vals = []
vals = [1]*num_1s_needed
# What the random numbers need to sum to. It's X minus all the ones.
sum_needed = X - num_1s_needed
if sum_needed == 0:
return vals
elif sum_needed == 1:
idx = random.randrange(num_1s_needed-1)
vals.insert(idx, 1)
return vals
else:
# places_left determines how many random numbers to generate.
places_left = Y - num_1s_needed
random_vals = [random.randrange(1, sum_needed) for i in range(places_left)]
# If sum of random_vals is too small, add randomly to a value as needed.
while sum(random_vals) < sum_needed:
idx_to_add_to = random.randrange(len(random_vals))
if random_vals[idx_to_add_to] < X:
random_vals[idx_to_add_to] = 1
# Sort from big to small and find the first instance of 1.
# Add all 1s to vals and remove from random_vals.
# Adjust sum_needed to always be X - len(vals).
random_vals.sort(reverse=True)
try:
idx_1 = random_vals.index(1)
orig_length = len(random_vals)
random_vals = random_vals[:idx_1]
vals = [1]*(orig_length - len(random_vals))
sum_needed -= (orig_length - len(random_vals))
# If there is no 1 in random_vals, function will go to the except block.
except:
pass
# Subtract as needed from a random value that is greater than 1.
while sum(random_vals) > sum_needed:
idx_to_subtract_from = random.randrange(len(random_vals))
if random_vals[idx_to_subtract_from] > 1:
random_vals[idx_to_subtract_from] -= 1
# Generate a random index and insert each random_val into vals at that index.
for i in range(len(random_vals)):
idx = 0
if len(vals) > 1:
idx = random.randrange(len(vals))
vals.insert(idx, random_vals[i])
return vals
# Change X and Y to your liking.
vals = get_vals_from_X_Y(X, Y)
Let me know if anything needs clarification.