Reputation: 1047
I have a for loop where at each turn I choose a random number from a constant range. However, I never want to get a duplicate value. This is how I am doing it now:
import random
my_range = 1000
chosen = [81, 944, 576, 618, 333, 350, 579, 774, 86, 511, 619, 552, 804, 44, 894, 408, 242]
for i in range(500):
rand_ind = random.randint(0,1000)
while rand_ind in chosen:
rand_ind = random.randint(0,my_range)
chosen.append(rand_ind)
print(chosen)
Is there any way to do this in one line or more efficiently?
Upvotes: 0
Views: 131
Reputation: 50096
Directly use random.sample
to get a fixed number of picks without repetition.
chosen = random.sample(range(1001), 500)
If you want to exclude some numbers before sampling, use intermediate set
s for efficient differences.
excluded = {81, 944, 576, 618, 333, 350, 579, 774, 86, 511, 619, 552, 804, 44, 894, 408, 242}
chosen = random.sample(list(set(range(1001)) - excluded), 500)
Upvotes: 4
Reputation: 13087
Depending on the size of the range (assuming it is not too big), I would use shuffle on all the possible items.
import random
my_range = 1000
my_item_count = my_range //2
# generate all the possibilities
choices = list(range(my_range))
# randomize their order
random.shuffle(choices)
# take the first n
choices = choices[:my_item_count]
print(choices)
If I set my_range
to a number like 10, we get a result like:
[7, 3, 0, 4, 8]
There was a valid question asked about if this in fact is any faster than the original answer. That seem like a perfectly reasonable question and I probably should have included timings to demonstrate.
Hint, if it was not then I would not have offered it as an answer :-)
Let's add some timing code. This will compare the original answer and this answer but I encourage others providing answers to demonstrate their improvements as well. Note I added the answer by MisterMiyagi as I suspected it would be fastest of all being a single call.
Hint, it was :-)
import timeit
setup_original='''
import random
my_range = 1_000
my_item_count = my_range // 2
def test(my_range, my_item_count):
chosen = []
for i in range(my_item_count):
rand_ind = random.randint(0,my_range)
while rand_ind in chosen:
rand_ind = random.randint(0,my_range)
chosen.append(rand_ind)
return chosen
'''
setup_jonsg='''
import random
my_range = 1_000
my_item_count = my_range // 2
def test(my_range, my_item_count):
choices = list(range(my_range))
random.shuffle(choices)
return choices[:my_item_count]
'''
setup_MisterMiyagi='''
import random
my_range = 1_000
my_item_count = my_range // 2
def test(my_range, my_item_count):
return random.sample(range(my_range), my_item_count)
'''
print("Original: %s" % timeit.timeit("test(my_range, my_item_count)", setup=setup_original, number=1000))
print("jonsg: %s" % timeit.timeit("test(my_range, my_item_count)", setup=setup_jonsg, number=1000))
print("MisterMiyagi: %s" % timeit.timeit("test(my_range, my_item_count)", setup=setup_MisterMiyagi, number=1000))
On my laptop the result is:
Original: 2.9269699
jonsg: 0.3391961000000001
MisterMiyagi: 0.2070141999999997
The original is about 10x SLOWER!
Answer by MisterMiyagi is 50% FASTER than mine!
Upvotes: 1
Reputation: 9
import random
def shuffle(Arr):
arr = [i for i in Arr]
for i in range(len(arr) - 1):
rndIdx = random.randint(i, len(arr)-1)
tmp = arr[rndIdx]
arr[rndIdx] = arr[i]
arr[i] = tmp
return arr
In[0]: a = list(range(30)); print(a) #my_range
Out[0]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
In[1]: b = list(range(15, 25)); print(b) #chosen
Out[1]: [15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
In[2]: l = [i for i in a if i not in b]; print(l) #my_range - chosen
Out[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 25, 26, 27, 28, 29]
In[3]: print(shuffle(l)) #shuffled(my_range - chosen) without repeating elements
Out[3]: [5, 2, 8, 6, 1, 26, 28, 11, 4, 7, 13, 9, 14, 29, 12, 10, 0, 27, 3, 25]
Upvotes: -1
Reputation: 27599
Looks like you want to sample
from set(range(1001)) - set(chosen)
.
Upvotes: 2