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

后端 未结 2 1549
慢半拍i
慢半拍i 2021-01-23 02:13

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 fo

相关标签:
2条回答
  • 2021-01-23 02:22

    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.

    0 讨论(0)
  • 2021-01-23 02:41

    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
    
    0 讨论(0)
提交回复
热议问题