问题
I have a block of code with
import random as r
value = r.choice(r.randint(10, 30), r.randint(50, 100))
print (value)
Why does it give me the following error, and how should I fix it?
TypeError: choice() takes 2 positional arguments but 3 were given
回答1:
choice
takes a sequence of items to choose from, not varargs. Wrap the arguments to make an anonymous tuple
or list
and it'll work, changing:
value = r.choice(r.randint(10, 30), r.randint(50, 100))
to either of:
value = r.choice((r.randint(10, 30), r.randint(50, 100))) # Tuple
value = r.choice([r.randint(10, 30), r.randint(50, 100)]) # List
回答2:
random.choice accepts a single sequence. You have a couple of options as to how you generate the number you want.
To fix the immediate error, make the input a sequence:
value = r.choice([r.randint(10, 30), r.randint(50, 100)])
This is not a very good solution because it generates two numbers, which is wasteful. Furthermore, the resulting distribution is conditioned on the sizes of the ranges, which is likely incorrect.
A more general way to solve this is to generate a single number and map it to the range you want:
v = r.randint(0, 21 + 51)
value = v + 10 if v <= 20 else v - 21 + 50
If you want to specify the inputs as ranges and get a uniform distribution across all of them, you can generalize like this:
def from_ranges(*ranges):
n = sum(map(len, ranges))
v = random.randrange(n)
for range in ranges:
k = len(range)
if v < k:
return range[v]
v -= k
This is nice because the ranges are small objects that don't take up a lot of space, but allow you a lot of flexibility in terms of what you can specify.
For the example in your question, you would call the function as
>>> from_ranges(range(10, 31), range(50, 101))
You could also eliminate the final linear search in favor of a binary search by accumulating the lengths into a cumulative sum:
from bisect import bisect
from itertools import accumulate
from random import randrange
def from_ranges(*ranges):
lengths = list(accumulate(map(len, ranges)))
v = randrange(lengths[-1])
r = bisect(lengths, v)
offset = lengths[r - 1] if r > 0 else 0
return ranges[r][v - offset]
This solution would be faster if you were to memoize the accumulation, before the random number is generated. Otherwise, it offers little advantage over the first solution.
来源:https://stackoverflow.com/questions/61051333/getting-1-number-from-2-ranges-with-choice-and-randint