1

First of all, I'd like to say that I thought the exact same question had been answered before, but after a brief research I couldn't find any threads leading me to the answer that i wanted in here which means i didn't dig enough or missed some keywords. Sorry for that if that question is out there.

Anyways, I've started to learn python and was going through some exercises. I needed to create a list which has 10 randomly generated integers in it and each integer must have a different value.

So I've tried to compare the first element of the list with the next one and if they're the same, I tried to generate a new number with the if statement.

import random

listA = []

for i in range(0,10):
    x=random.randint(1,100)

    if listA[i] == listA[i-1]:
        x=random.randint(1,100)
    else:
        listA.append(x)

listA.sort()
print(listA)

But I've got an error saying; "list index is out of range"

I expected the if statement to start with the "0" index and count till the 9th index of the "listA" and as it goes, compare them, if the same, generate another random number. But obviously, my indexation was wrong.

Also, any other comments on the code would be appreciated.

Thanks for your time in advance.

Rustang
  • 546
  • 5
  • 15
  • 3
    You are doing this `if listA[i] == listA[i-1]` when the list is empty – Dani Mesejo Feb 05 '19 at 13:16
  • 1
    Try using `if x in listA:` and you can use `random.sample(range(1, 100), 10)` for making unique list – seuling Feb 05 '19 at 13:19
  • 1
    You want to check the previous element except for the first iteration, so `if i>0 and listA[i] == listA[i-1]` will solve your problem. – Guimoute Feb 05 '19 at 13:27
  • Thank you. That was helpful. Like i said, since i'm really new to the python syntax and i'm learning with exercises, i didn't really know that was possible. Also, random.sample is totally new to me. – Wezz Westbrook Feb 05 '19 at 13:32

3 Answers3

4

In Python, a set can only contain unique values, so in the following code, duplicate random numbers won't increase the length of the set:

import random

s = set()
while len(s) < 10:
    s.add(random.randint(1,100))

print(sorted(s))

Output:

[18, 20, 26, 48, 51, 72, 75, 92, 94, 99]
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
3

Try the following.

import random

listA = []

while(len(listA) < 10):
    x = random.randint(1,100)
    if x not in listA:
        listA.append(x)

listA.sort()
print(listA)

Explanation:

You should use a while loop so that you keep generating numbers until your desired list is actually 10 numbers. When using the for loop, if you happen to generate [2, 2, 30, 40, 2, 10, 20, 83, 92, 29] at random, your list will only be 8 numbers long because the duplicate 2's will not be added, although you have already looped through your for loop 10 times.

While is the key here as you will never be able to fool proof predict how many times it will take to randomly have 10 different numbers, therefore you want to keep going while you haven't reached the desired length.

Also, the keyword in is a simple way to check if something already exists inside something else.

Rustang
  • 546
  • 5
  • 15
  • 2
    Good answer. I'd also recommend `random.sample` as a better alternative to the `while` loop. – jpp Feb 05 '19 at 13:26
  • 1
    Thank you, that worked and your explanation was understandable. – Wezz Westbrook Feb 05 '19 at 13:29
  • @jpp why not provide a separate answer which uses `random.sample`? – Risadinha Feb 05 '19 at 13:35
  • @Risadinha, Because that's technically another question: [How do I create a list of random numbers without duplicates?](https://stackoverflow.com/questions/9755538/how-do-i-create-a-list-of-random-numbers-without-duplicates). It's generally good when answering to indicate obvious and efficient alternatives, even if it goes technically beyond the original question. – jpp Feb 05 '19 at 13:37
  • I've added an example using sampling without replacement - seems like a more logical way of attacking the problem to me, but agree this answer is closer to the original question. – ajrwhite Feb 05 '19 at 13:42
  • @jpp I'm feeling too stupid to grasp the "technical difference" here. Now that you've pointed out the other question I think it provides a practical answer to this one? Aren't they duplicates for the goal of "creating a list of unique random integers"? – Risadinha Feb 05 '19 at 14:51
  • 1
    @Risadinha, The *core* problem, in my opinion, is the OP's logic is technically incorrect. The *other* issue, how you can get the same result more efficiently, is also a valid concern, but doesn't explain OP's misunderstanding. – jpp Feb 05 '19 at 14:53
  • Added timings for all the suggestions to https://stackoverflow.com/a/54535557/5447172 – ajrwhite Feb 05 '19 at 16:03
3

This can be thought of as sampling without replacement. In this case, you are "sampling" 10 items at random from range(1, 101) and each item that is sampled can only be sampled once (i.e. it is not "replaced" - imagine drawing numbered balls at random from a bag to understand the concept).

Sampling without replacement can be handled in one line:

import random
listA = random.sample(range(1, 101), 10)

Another way of thinking about it is to shuffle list(range(1, 101)) and take the first 10 elements:

import random
listA = list(range(1, 101))
random.shuffle(listA)
listA[:10]

Timing the different approaches

Using the %timeit magic in iPython we can compare the different approaches suggested in the answers:

def random_sample():
    import random
    return sorted(random.sample(range(1, 101), 10))

def random_shuffle():
    import random
    listA = list(range(1, 101))
    random.shuffle(listA)
    return sorted(listA[:10])

def while_loop():
    import random
    listA = []
    while(len(listA) < 10):
        x = random.randint(1, 100)
        if x not in listA:
            listA.append(x)
    return sorted(listA)

def random_set():
    import random
    s = set()
    while len(s) < 10:
        s.add(random.randint(1, 100))
    return sorted(s)

%timeit for i in range(100): random_sample()
# 1.38 ms ± 17.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit for i in range(100): random_shuffle()
# 6.81 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit for i in range(100): while_loop()
# 1.61 ms ± 18.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit for i in range(100): set_approach()
# 1.48 ms ± 19.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
ajrwhite
  • 7,728
  • 1
  • 11
  • 24
  • 1
    NB - the equivalent of `random.randint(1,100)` using a range would require `range(1,101)` – T Burgis Feb 05 '19 at 13:49
  • 1
    Note that the builtin `random.sample` "is especially fast and space efficient for sampling from a large population" https://docs.python.org/3.7/library/random.html#random.sample – Risadinha Feb 05 '19 at 15:02