After this you want to look into a better way to generate these random numbers. At first I used an algorithm that generates \$n\$ random numbers that add to a number. As then we can use an algorithm documented by David Schwartz algorithm documented by David Schwartz. To prove that we can use this algorithm rather than the one that you are using, we have two of the numbers in the mean equation:
After this you want to look into a better way to generate these random numbers. At first I used an algorithm that generates \$n\$ random numbers that add to a number. As then we can use an algorithm documented by David Schwartz. To prove that we can use this algorithm rather than the one that you are using, we have two of the numbers in the mean equation:
After this you want to look into a better way to generate these random numbers. At first I used an algorithm that generates \$n\$ random numbers that add to a number. As then we can use an algorithm documented by David Schwartz. To prove that we can use this algorithm rather than the one that you are using, we have two of the numbers in the mean equation:
Instead ifFirst off I'd recommend that you thinkcreate a helper function. This is as thinking of algorithms as \0ドル-\text{stop}\$ with a step of\1ドル\$ is easier than thinking of \$\text{start}-\text{stop}\$ with a step of \$\text{step}\$. To do this assignmentyou can perform a transform on the output of generatingyou function so that you multiply by the step, and add by the start. But you should take into account that some means are not possible with certain steps. Finally if you make your input work like a Python range, then it'll be familiar to other Python programmers. And so you could get a decorator like:
from functools import wraps
from math import copysign
def normalize_input(fn):
@wraps(fn)
def wrapper(mean, length, start, stop=None, step=None):
if stop is None:
stop = start
start = 0
if step is None:
step = 1
if not (start <= mean * copysign(1, step) < stop):
raise ValueError('mean is not within distribution range.')
if mean * length % step != 0:
raise ValueError('unreachable mean.')
stop -= start
mean -= start
if step != 1:
stop /= step
mean /= step
numbers = fn(mean, length, stop)
for i, num in enumerate(numbers):
numbers[i] = num * step + start
return numbers
return wrapper
This allows you to define functions that only take a mean, length and stop. And so could make your code:
@normalize_input
def list_with_mean(mean, length, stop):
HT = [x for x in range(stop+1)]
target_mean = 0
while target_mean != mean:
outcomes = [random.choice(HT) for x in range(length)]
target_mean = sum(outcomes)/len(outcomes)
return outcomes
numbers = list_with_mean(50, 100, 25, 75)
Which will generate \100ドル\$ random numbers with a mean of \50ドル\$, and with numbers ranging from \25ドル-75\$.
After this you want to look into a better way to generate these random numbers. At first I used an algorithm that generates \$n\$ random numbers that add to a number,. then a quick searchAs then we can get you ause an brilliant algorithmalgorithm documented by David Schwartz. The reasonTo prove that we can add to a specific number is as that's a re-arrangementuse this algorithm rather than the one that you are using, we have two of the numbers in the mean equation:
from random import randrange
from itertools import tee
# Itertools Recipe
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
def fixed_sum(total, length):
for a, b in pairwise(sorted([0, total] + [randrange(total) for _ in range(length - 1)])):
yield b - a
def bias_mean(lower=False):
def bias_mean_inner(fn):
@wraps(fn)
def wrapper(mean, length, stop):
flip_mean = (mean > stop / 2) is lower
if flip_mean:
mean = stop - mean
numbers = fn(mean, length, stop)
if flip_mean:
for i, num in enumerate(numbers):
numbers[i] = stop - num
return numbers
return wrapper
return bias_mean_inner
@normalize_input
@bias_mean(True)
def by_sum(mean, length, stop):
numbers = list(fixed_sum(int(mean * length), length))
add = 0
while True:
for i in range(0, length):
num = numbers[i]
if num >= stop:
add += (num - stop)
numbers[i] = stop
else:
if add + num > stop:
n = stop
add -= stop - num
else:
n = num + add
add = 0
numbers[i] = n
if add == 0:
break
return numbers
And then you just need to wrap fixed_sum
As pointed out by @Graipher this algorithm isn't very good to outputgenerate the random numbers between certain ranges.
If you change you function to take; start
, stop
, and step
And so instead of using the above algorithm, weyou can implementuse one that adds a range
like selectionrandom amount to each number, as long as it doesn't exceed the maximum size.
AfterTo do this you can create an amount variable that you subtract from, when you generate a random number.
You then want to check ifloop through you list and pick a random number to add to the inputcurrent item.
The domain of this random item is actually valid\0ドル-\min(\text{amount}, \text{stop} - \text{item})\$. And
This is so asking for a mean outside the range should error, along with a mean that is unreachableit doesn't exceed the maximum number size, withinand all the rangeitems add to the total we want.
ToThis can get you the code:
@normalize_input
@bias_mean(False)
def list_with_meanby_sum_distribution(mean, length, start, stop=None, step=Nonestop):
if stopnumbers is= None:
[0] * length
amount = stoplength =* startmean
while amount:
start = 0
iffor stepi isin Nonerange(length):
step = 1
ifn not= randrange(start <= mean * copysignmin(1amount, stepstop - numbers[i]) <+ stop1):
raise ValueError('mean is not withinnumbers[i] distrobution+= range.')n
if mean * length % step != 0:
amount -= n
raise ValueError('unreachablereturn mean.')numbers
After this you need to actually implement the extra options. If the start is not zero then you want to add start to each of your output numbers. And, if the step is not one then you want to multiply the output by the step.
After this you want to reduce the numbers output by fixed_sum
so they don't go above the maximum, but you can't just throw away the numbers.
And so at the cost of a uniform distribution I add the extra to the next number. But as this makes long streaks of 100's at high numbers, you can invert the mean, and then subtract the numbers from stop
if the mean goes above '(a + b) /2'.
This got me:has a uniform output when the \$\text{mean} = \frac{\text{stop}}{2}\$, but otherwise the beta function is much better.
def numbers_with_mean(mean, length, start, stop=None, step=None):
if stop is None:
stop = start
start = 0
if step is None:
step = 1
if not (start <= mean * copysign(1, step) < stop):
raise ValueError('mean is not within distribution range.')
if mean * length % step != 0:
raise ValueError('unreachable mean.')
# shift domain from start-stop with a step of step, to 0-(start-stop)/step
k = 0
if start != 0:
k = start
start = 0
stop -= k
mean -=k
if step != 1:
start //= step
stop //= step
mean /= step
# Get better distribution on larger means
if mean > (stop / 2):
numbers = list(fixed_sum(int((stop - mean) * length), length))
else:
numbers = list(fixed_sum(int(mean * length), length))
# Reassign numbers with overflow to next number.
add = 0
while True:
for i in range(0, length):
num = numbers[i]
if num >= stop:
add += (num - stop)
numbers[i] = stop
else:
if add + num > stop:
n = stop
add -= stop - num
else:
n = num + add
add = 0
numbers[i] = n
if add == 0:
break
if mean > (stop / 2):
for i, num in enumerate(numbers):
numbers[i] = stop - numbers[i]
# shift data to the from 0-(start-stop)/step to start-stop with a step of step.
for i, num in enumerate(numbers):
numbers[i] = num * step + k
return numbers
Instead if you think of this assignment of generating \$n\$ numbers that add to a number, then a quick search can get you a brilliant algorithm documented by David Schwartz. The reason that we can add to a specific number is as that's a re-arrangement of the mean equation:
from random import randrange
from itertools import tee
# Itertools Recipe
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
def fixed_sum(total, length):
for a, b in pairwise(sorted([0, total] + [randrange(total) for _ in range(length - 1)])):
yield b - a
And then you just need to wrap fixed_sum
to output numbers between certain ranges.
If you change you function to take; start
, stop
, and step
, we can implement a range
like selection.
After this you want to check if the input is actually valid. And so asking for a mean outside the range should error, along with a mean that is unreachable, within the range.
To get:
def list_with_mean(mean, length, start, stop=None, step=None):
if stop is None:
stop = start
start = 0
if step is None:
step = 1
if not (start <= mean * copysign(1, step) < stop):
raise ValueError('mean is not within distrobution range.')
if mean * length % step != 0:
raise ValueError('unreachable mean.')
After this you need to actually implement the extra options. If the start is not zero then you want to add start to each of your output numbers. And, if the step is not one then you want to multiply the output by the step.
After this you want to reduce the numbers output by fixed_sum
so they don't go above the maximum, but you can't just throw away the numbers.
And so at the cost of a uniform distribution I add the extra to the next number. But as this makes long streaks of 100's at high numbers, you can invert the mean, and then subtract the numbers from stop
if the mean goes above '(a + b) /2'.
This got me:
def numbers_with_mean(mean, length, start, stop=None, step=None):
if stop is None:
stop = start
start = 0
if step is None:
step = 1
if not (start <= mean * copysign(1, step) < stop):
raise ValueError('mean is not within distribution range.')
if mean * length % step != 0:
raise ValueError('unreachable mean.')
# shift domain from start-stop with a step of step, to 0-(start-stop)/step
k = 0
if start != 0:
k = start
start = 0
stop -= k
mean -=k
if step != 1:
start //= step
stop //= step
mean /= step
# Get better distribution on larger means
if mean > (stop / 2):
numbers = list(fixed_sum(int((stop - mean) * length), length))
else:
numbers = list(fixed_sum(int(mean * length), length))
# Reassign numbers with overflow to next number.
add = 0
while True:
for i in range(0, length):
num = numbers[i]
if num >= stop:
add += (num - stop)
numbers[i] = stop
else:
if add + num > stop:
n = stop
add -= stop - num
else:
n = num + add
add = 0
numbers[i] = n
if add == 0:
break
if mean > (stop / 2):
for i, num in enumerate(numbers):
numbers[i] = stop - numbers[i]
# shift data to the from 0-(start-stop)/step to start-stop with a step of step.
for i, num in enumerate(numbers):
numbers[i] = num * step + k
return numbers
First off I'd recommend that you create a helper function. This is as thinking of algorithms as \0ドル-\text{stop}\$ with a step of\1ドル\$ is easier than thinking of \$\text{start}-\text{stop}\$ with a step of \$\text{step}\$. To do this you can perform a transform on the output of you function so that you multiply by the step, and add by the start. But you should take into account that some means are not possible with certain steps. Finally if you make your input work like a Python range, then it'll be familiar to other Python programmers. And so you could get a decorator like:
from functools import wraps
from math import copysign
def normalize_input(fn):
@wraps(fn)
def wrapper(mean, length, start, stop=None, step=None):
if stop is None:
stop = start
start = 0
if step is None:
step = 1
if not (start <= mean * copysign(1, step) < stop):
raise ValueError('mean is not within distribution range.')
if mean * length % step != 0:
raise ValueError('unreachable mean.')
stop -= start
mean -= start
if step != 1:
stop /= step
mean /= step
numbers = fn(mean, length, stop)
for i, num in enumerate(numbers):
numbers[i] = num * step + start
return numbers
return wrapper
This allows you to define functions that only take a mean, length and stop. And so could make your code:
@normalize_input
def list_with_mean(mean, length, stop):
HT = [x for x in range(stop+1)]
target_mean = 0
while target_mean != mean:
outcomes = [random.choice(HT) for x in range(length)]
target_mean = sum(outcomes)/len(outcomes)
return outcomes
numbers = list_with_mean(50, 100, 25, 75)
Which will generate \100ドル\$ random numbers with a mean of \50ドル\$, and with numbers ranging from \25ドル-75\$.
After this you want to look into a better way to generate these random numbers. At first I used an algorithm that generates \$n\$ random numbers that add to a number. As then we can use an algorithm documented by David Schwartz. To prove that we can use this algorithm rather than the one that you are using, we have two of the numbers in the mean equation:
from random import randrange
from itertools import tee
# Itertools Recipe
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
def fixed_sum(total, length):
for a, b in pairwise(sorted([0, total] + [randrange(total) for _ in range(length - 1)])):
yield b - a
def bias_mean(lower=False):
def bias_mean_inner(fn):
@wraps(fn)
def wrapper(mean, length, stop):
flip_mean = (mean > stop / 2) is lower
if flip_mean:
mean = stop - mean
numbers = fn(mean, length, stop)
if flip_mean:
for i, num in enumerate(numbers):
numbers[i] = stop - num
return numbers
return wrapper
return bias_mean_inner
@normalize_input
@bias_mean(True)
def by_sum(mean, length, stop):
numbers = list(fixed_sum(int(mean * length), length))
add = 0
while True:
for i in range(0, length):
num = numbers[i]
if num >= stop:
add += (num - stop)
numbers[i] = stop
else:
if add + num > stop:
n = stop
add -= stop - num
else:
n = num + add
add = 0
numbers[i] = n
if add == 0:
break
return numbers
As pointed out by @Graipher this algorithm isn't very good to generate the random numbers. And so instead of using the above algorithm, you can use one that adds a random amount to each number, as long as it doesn't exceed the maximum size. To do this you can create an amount variable that you subtract from, when you generate a random number. You then want to loop through you list and pick a random number to add to the current item. The domain of this random item is \0ドル-\min(\text{amount}, \text{stop} - \text{item})\$. This is so that it doesn't exceed the maximum number size, and all the items add to the total we want. This can get you the code:
@normalize_input
@bias_mean(False)
def by_sum_distribution(mean, length, stop):
numbers = [0] * length
amount = length * mean
while amount:
for i in range(length):
n = randrange(min(amount, stop - numbers[i]) + 1)
numbers[i] += n
amount -= n
return numbers
This has a uniform output when the \$\text{mean} = \frac{\text{stop}}{2}\$, but otherwise the beta function is much better.
def numbers_with_mean(mean, length, start, stop=None, step=None):
if stop is None:
stop = start
start = 0
if step is None:
step = 1
if not (start <= mean * copysign(1, step) < stop):
raise ValueError('mean is not within distribution range.')
if mean * length % step != 0:
raise ValueError('unreachable mean.')
# shift domain from start-stop with a step of step, to 0-(start-stop)/step
k = 0
if start != 0:
k = start
start = 0
stop -= k
mean -=k
if step != 1:
start //= step
stop //= step
mean /= step
# Get better distribution on larger means
if mean > (stop / 2):
numbers = list(fixed_sum(int((stop - mean) * length), length))
else:
numbers = list(fixed_sum(int(mean * length), length))
# Reassign numbers with overflow to next number.
add = 0
while True:
for i in range(0, length):
num = numbers[i]
if num >= stop:
add += (num - stop)
numbers[i] = stop * step + k
else:
if add + num > stop:
n = stop
add -= stop - num
else:
n = num + add
add = 0
numbers[i] = n * step + k
if add == 0:
break
if mean > (stop / 2):
for i, num in enumerate(numbers):
numbers[i] = stop - numbers[i]
# shift data to the from 0-(start-stop)/step to start-stop with a step of step.
for i, num in enumerate(numbers):
numbers[i] = num * step + k
return numbers
def numbers_with_mean(mean, length, start, stop=None, step=None):
if stop is None:
stop = start
start = 0
if step is None:
step = 1
if not (start <= mean * copysign(1, step) < stop):
raise ValueError('mean is not within distribution range.')
if mean * length % step != 0:
raise ValueError('unreachable mean.')
k = 0
if start != 0:
k = start
start = 0
stop -= k
mean -=k
if step != 1:
start //= step
stop //= step
mean /= step
if mean > (stop / 2):
numbers = list(fixed_sum(int((stop - mean) * length), length))
else:
numbers = list(fixed_sum(int(mean * length), length))
add = 0
while True:
for i in range(0, length):
num = numbers[i]
if num >= stop:
add += (num - stop)
numbers[i] = stop * step + k
else:
if add + num > stop:
n = stop
add -= stop - num
else:
n = num + add
add = 0
numbers[i] = n * step + k
if add == 0:
break
if mean > (stop / 2):
for i, num in enumerate(numbers):
numbers[i] = stop - numbers[i]
return numbers
def numbers_with_mean(mean, length, start, stop=None, step=None):
if stop is None:
stop = start
start = 0
if step is None:
step = 1
if not (start <= mean * copysign(1, step) < stop):
raise ValueError('mean is not within distribution range.')
if mean * length % step != 0:
raise ValueError('unreachable mean.')
# shift domain from start-stop with a step of step, to 0-(start-stop)/step
k = 0
if start != 0:
k = start
start = 0
stop -= k
mean -=k
if step != 1:
start //= step
stop //= step
mean /= step
# Get better distribution on larger means
if mean > (stop / 2):
numbers = list(fixed_sum(int((stop - mean) * length), length))
else:
numbers = list(fixed_sum(int(mean * length), length))
# Reassign numbers with overflow to next number.
add = 0
while True:
for i in range(0, length):
num = numbers[i]
if num >= stop:
add += (num - stop)
numbers[i] = stop
else:
if add + num > stop:
n = stop
add -= stop - num
else:
n = num + add
add = 0
numbers[i] = n
if add == 0:
break
if mean > (stop / 2):
for i, num in enumerate(numbers):
numbers[i] = stop - numbers[i]
# shift data to the from 0-(start-stop)/step to start-stop with a step of step.
for i, num in enumerate(numbers):
numbers[i] = num * step + k
return numbers