Random Crap Xemko
Random Crap Xemko

Reputation: 25

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

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 

Upvotes: 2

Views: 105

Answers (2)

Mad Physicist
Mad Physicist

Reputation: 114320

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.

Upvotes: 1

ShadowRanger
ShadowRanger

Reputation: 155418

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

Upvotes: 2

Related Questions