Getting 1 number from 2 ranges with choice() and randint()

廉价感情. 提交于 2020-07-03 08:25:56

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!