简体   繁体   中英

Generate random numbers only with specific digits

How do I generate a random number from a specific set of digits? For example,
I want to generate numbers from the range 1-100,000 such that every number has only odd digits (for example: 111 , 1351 , 19711 etc..)

Using the random module I tried:

import random

rand = random.randint([1, 3, 5, 7, 9]) 

Is there any efficient way of doing it?
Thank you.

One way could be to define a list of odds from which to sample, but keeping in mind the how likely it should be for a number to be sampled randomly. Since there are ten times as many 2 digit numbers than 1 digit numbers, we need to set the weights of these sampling sizes according to this logic.

Following this reasoning, we could use numpy.random.choice , which allows for sampling from a list following a probability distribution:

from numpy.random import choice

odds = ['1','3','5','7','9']

n_digits = 5 # up to 99999 for ex
range_digits = list(range(1,n_digits))

weights = [5**i for i in range_digits]
weights_sum = sum(weights)
probs = [i/weights_sum for i in weights]

sizes = choice(range_digits,size=n,p=probs)
[int(''.join(choice(odds,size))) for size in sizes]
# [3151, 3333, 1117, 7577, 1955, 1793, 5713, 1595, 5195, 935]

Let's check the generated distribution for 10_000 samples:

from collections import Counter

sizes = choice(range_digits,size=10_000,p=probs)
out = [int(''.join(choice(odds,size))) for size in sizes]

Counter(len(str(i)) for i in out)
# Counter({4: 8099, 3: 1534, 2: 304, 1: 63})

Here is a solution using list-comprehension:

>>> random.sample([i for i in range(1,100_001) if all([int(x)%2==1 for x in str(i)])], 4)
[3115, 75359, 53159, 31771]

As pointed out in the comments below, the above code becomes more and more inefficient the larger the numbers get, due to all numbers being checked if each of them includes only odd-numbers. That includes numbers that are even.

IF we add another filter to first remove all even-numbers we reduce the amounts of comparisons that are being made by about a third.

Here is a quick comparison between the two:

import datetime
import random

def timer(var):
    def wrapper(*args, **kwargs):
        start = datetime.datetime.now()
        result = var()
        print(f"Elapsed time: {datetime.datetime.now()-start}")
        return result
    return wrapper

@timer
def allNumbers():
    return random.sample([i for i in range(1, 1_000_001) if all([int(x) % 2 == 1 for x in str(i)])], 4)

@timer
def oddNumbers():
    return random.sample([i for i in [x for x in range(1, 1_000_001) if x % 2 == 1] if all([int(x) % 2 == 1 for x in str(i)])], 4)

print("Calling allNumbers:")
print(allNumbers())
print("Calling oddNumbers:")
print(oddNumbers())

Output:

Calling allNumbers:
Elapsed time: 0:00:05.119071
[153539, 771197, 199379, 751557]
Calling oddNumbers:
Elapsed time: 0:00:02.978188
[951919, 1399, 199515, 791393]

Given the range you want is 1 - 100,000 you will never pick 100,000 because it has even digits: 0. Effectively your maximum allowed output is 99,999. That is five digits.

Your output might have invisible leading zeros: 975 is allowed, which is effectively 00975. Those leading zeros have to be allowed for in your code.

  1. Pick how many leading zeros you have. 10% of the numbers in your range have a leading zero. 10% of those have a second leading zero. 10% of those (1 in 1,000) have a third leading zero and so on. Five leading zeros is 0 output. That is outside the allowed range, so you will need to start picking again. That will only happen very rarely.

  2. Once you know how many leading zeros there are, you know how many other digits you need. Pick that many digits from the allowed digit list: [1, 3, 5, 7, 9].

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM