random.choice from set? python

Question:

I’m working on an AI portion of a guessing game. I want the AI to select a random letter from this list. I’m doing it as a set so I can easily remove letters from the list as they are guessed in the game and are therefore no longer available to be guessed again.

it says set object isn’t indexable. How can I work around this?

import random 
aiTurn=True

while aiTurn == True:
    allLetters = set(list('abcdefghijklmnopqrstuvwxyz'))
    aiGuess=random.choice(allLetters)



    print (aiGuess) 
Asked By: jamyn

||

Answers:

Note (Oct. 2020): as of v3.9, Python has officially deprecated random.sample() working on sets, with the official guidance being to explicitly convert the set to a list or tuple before passing it in, though this doesn’t solve the efficiency problems.


>>> random.sample(set('abcdefghijklmnopqrstuvwxyz'), 1)
['f']

Documentation: https://docs.python.org/3/library/random.html#random.sample

Note that choosing random elements from a set is extremely inefficient no matter how you do it – it takes time proportional to the size of the set, or worse if the set’s underlying hash table is sparse due to removed elements.

Instead, you should probably use a different data structure that supports this operation efficiently.

Answered By: NPE

You should use random.choice(tuple(myset)), because it’s faster and arguably cleaner looking than random.sample. I wrote the following to test:

import random
import timeit

bigset = set(random.uniform(0,10000) for x in range(10000))

def choose():
    random.choice(tuple(bigset))

def sample():
    random.sample(bigset,1)[0]

print("random.choice:", timeit.timeit(choose, setup="global bigset", number=10000)) # 1.1082136780023575
print("random.sample:", timeit.timeit(sample, setup="global bigset", number=10000)) # 1.1889629259821959

From the numbers it seems that random.sample takes 7% longer.

Answered By: Scott Ritchie

You could work around this by using a list instead of a set. You will still be able to remove letters "easily" from the list. Try this, for example:

allLetters = list('abcdefghijklmnopqrstuvwxyz')
aiGuess = random.choice(allLetters)
allLetters.remove(aiGuess)

Another option is to randomly choose the index instead of the letter, which might be slightly faster since we don’t need to search for the element to delete (but I’d question if speed actually matters here?):

allLetters = list('abcdefghijklmnopqrstuvwxyz')
index = random.randint(0, len(allLetters)-1) # Top is inclusive, unlike slices
aiGuess = allLetters[index]
del allLetters[index]
Answered By: Moot

Since the choice list is not very long, you can use random.shuffle the list first. Then iterate each element from the list. This avoid removing element from a list one by one and make your code cleaner.

Answered By: zerozero nine

If you want to get a random element from the set.

a = set()
for i in range(10):
    a.add(i)
a.pop() // gives a random element from a set
Answered By: Sai

You can combine a doubly-linked list and a dictionary, to create a set with O(1) random choice.

Answered By: serg06

You could try this:

def random_set_ele(set_: set):
    copy = set_
    return copy.pop()

test_set = set(["".join(["elem-", str(l)]) for l in range(0, 1000000)])
start = perf_counter()
print(random_set_ele(test_set))
print(perf_counter()-start)

Result:

elem-57221
0.00016391400276916102

The .pop() method for a set randomly extracts and returns an element and pops it out of the set. We make a copy of the set before popping the element so that the original list is not modified.

Answered By: Richard Feynman
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.