29

Before 3.6 I would simply use set.pop(). Now, sets are ordered and pop always removes from the start.

What do you think is the most pythonic way?

I'm not sure how I feel about set.remove(random.sample(set, 1)[0]).

Hélio Meira Lins
  • 456
  • 1
  • 4
  • 9
  • 1
    The point is that the order (or, for that matter, the previous lack thereof) is an implementation detail you shouldn't rely on. This was never the Pythonic way to do it, as the intention is unclear; if you want a random item, do it explicitly: `set.pop(random.choice(set))` (and note that `set` is a bad name; it shadows the built in). – jonrsharpe Jun 17 '17 at 13:39
  • 1
    @jonrsharpe that was my first try, but it raises `AttributeError`. btw, I agree that it wasn't very pythonic. – Hélio Meira Lins Jun 17 '17 at 13:42
  • 1
    Ah yes, because you can't index a set - I guess that's why [e.g. this](https://stackoverflow.com/a/15837796/3001761) suggests sample. Also you can't pass an argument to `set.pop`. – jonrsharpe Jun 17 '17 at 13:43
  • 3
    "Arbitrary" isn't the same thing as "random" anyway. So it doesn't matter if you're on python 3.6 or not. If you want randomness, the `random` module is the way to go. – Aran-Fey Jun 17 '17 at 13:44
  • There is always `set.discard(random.choice(set))`. – President James K. Polk Jun 17 '17 at 13:46
  • 2
    Sets aren't ordered anyway, even on Python 3.7+. `set.pop()` is still not random, but not because of any Python 3.7 change. It was never random. – user2357112 Jun 02 '23 at 01:04

2 Answers2

25

Python 3.10 and lower:

The set .pop() method does not accept a parameter, it removes an arbitrary (but not random) set value. https://docs.python.org/3.6/library/stdtypes.html#set.pop

Take a set sample with one element random.sample(s, 1) and use set.remove().

>>> s = set([1,2,3,4,5])
>>> el = random.sample(s, 1)[0]
>>> s.remove(el)
>>> print(s)
{1, 3, 4, 5}

The random.choice does not work because sets are not indexed.

>>> random.choice(s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.5/random.py", line 256, in choice
    return seq[i]
TypeError: 'set' object is not subscriptable
wjandrea
  • 28,235
  • 9
  • 60
  • 81
iurisilvio
  • 4,868
  • 1
  • 30
  • 36
  • 4
    I think it is cleaner to do `s.remove(random.choice(tuple(s)))` rather than the slightly complicated sample and subscript. Internally `random.sample()` just checks for and converts `set` to `tuple` anyway. – Duncan Apr 17 '20 at 13:09
  • 3
    `TypeError: Population must be a sequence. For dicts or sets, use sorted(d).` "Changed in version 3.11: The population must be a sequence. Automatic conversion of sets to lists is no longer supported." – dangel Jun 02 '23 at 00:46
0

For Python 3.9 and later, random sampling from a set has been deprecated, as mentioned by @dangel in a comment on another answer to this question, and as described in the last paragraph of the docs, a very relevant SO answer, and another SO question.

You must now sample from a list or tuple. (I tested a numpy.array and got the same TypeError as with a set. See numpy's choice function for an alternative approach if you're using arrays.) It seems the most straight-forward pythonic way to do this is probably

sampled_item = random.sample(list(my_set), 1)[0]
my_set.remove(sampled_item)

This is a little longer than OP's suggested one-liners, but it's also pretty readable and the intent is clear. The TypeError (TypeError: Population must be a sequence. For dicts or sets, use sorted(d).) recommends using sorted rather than list, but I don't see the point of sorting before random sampling see comments on this.

mightypile
  • 7,589
  • 3
  • 37
  • 42
  • 1
    The suggestion to sort before the random sampling is likely to get reproducible behavior in situations where that matters (e.g. a "random" unit test that actually seeds the RNG for reproducible results), where plain `list` would make the output dependent on the `set` construction order and (if applicable) the per-run hash seed. I agree that 99% of the time, `list` is adequate. – ShadowRanger Jul 20 '23 at 16:37