594 votes

Comment récupérer un élément d'un ensemble sans le supprimer ?

Supposons ce qui suit :

>>> s = set([1, 2, 3])

Comment obtenir une valeur (n'importe quelle valeur) à partir de s sans faire s.pop() ? Je veux laisser l'élément dans l'ensemble jusqu'à ce que je sois sûr de pouvoir le retirer - ce dont je ne peux être sûr qu'après un appel asynchrone à un autre hôte.

Rapide et sale :

>>> elem = s.pop()
>>> s.add(elem)

Mais connaissez-vous un meilleur moyen ? Idéalement en temps constant.

22 votes

Quelqu'un sait-il pourquoi cette fonction n'est pas déjà implémentée dans Python ?

5 votes

Quel est le cas d'utilisation ? Set n'a pas cette capacité pour une raison. Vous êtes censé itérer à travers lui et faire des opérations liées à l'ensemble comme union etc. sans en tirer des éléments. Par exemple next(iter({3,2,1})) retourne toujours 1 donc si vous pensiez que cela renverrait un élément aléatoire - ce ne serait pas le cas. Alors peut-être que vous utilisez la mauvaise structure de données ? Quel est le cas d'utilisation ?

1 votes

En rapport : stackoverflow.com/questions/20625579/ (Je sais, ce n'est pas la même question, mais il y a là des alternatives et des idées intéressantes).

755voto

Blair Conrad Points 56195

Deux options qui ne nécessitent pas de copier l'ensemble du jeu :

for e in s:
    break
# e is now an element from s

Ou...

e = next(iter(s))

Mais en général, les ensembles ne prennent pas en charge l'indexation ou le découpage en tranches.

4 votes

Cela répond à ma question. Hélas, je suppose que je vais quand même utiliser pop(), puisque l'itération semble trier les éléments. Je les préférerais dans un ordre aléatoire...

16 votes

Je ne pense pas que l'iter() trie les éléments - lorsque je crée un ensemble et que j'utilise pop() jusqu'à ce qu'il soit vide, j'obtiens un ordre cohérent (trié, dans mon exemple), et c'est la même chose que l'iterator - pop() ne promet pas un ordre aléatoire, juste arbitraire, comme dans "je ne promets rien".

0 votes

Vous avez tout à fait raison. Je n'ai pas suivi les changements depuis la version 1.5.2 et je ne suis pas très familier avec les itérateurs et les fonctions connexes. (Stack Overflow m'a déjà beaucoup appris.) Merci pour la suggestion. Je l'ai fait mienne :-)

177voto

John Points 5478

Le code le plus faible serait :

>>> s = set([1, 2, 3])
>>> list(s)[0]
1

Évidemment, cela créerait une nouvelle liste contenant chaque membre de l'ensemble, ce qui n'est pas idéal si votre ensemble est très grand.

26 votes

@augurar : Parce que ça permet de faire le travail de manière relativement simple. Et parfois, c'est tout ce qui compte dans un script rapide.

4 votes

@augurar Je pense que les gens ont voté pour cette réponse parce que set n'est pas fait pour l'indexation et le découpage principalement ; et cet utilisateur a juste poussé le codeur à utiliser le type de données approprié pour ce travail, c'est-à-dire list .

9 votes

@Vicrobot Oui mais il le fait en copiant la collection entière et en transformant une opération O(1) en une opération O(n). C'est une solution terrible que personne ne devrait jamais utiliser.

40voto

wr. Points 1887

Pour donner quelques chiffres sur le timing des différentes approches, considérons le code suivant. Le get() est mon ajout personnalisé au setobject.c de Python, étant juste un pop() sans enlever l'élément.

from timeit import *

stats = ["for i in xrange(1000): iter(s).next()   ",
         "for i in xrange(1000): \n\tfor x in s: \n\t\tbreak",
         "for i in xrange(1000): s.add(s.pop())   ",
         "for i in xrange(1000): s.get()          "]

for stat in stats:
    t = Timer(stat, setup="s=set(range(100))")
    try:
        print "Time for %s:\t %f"%(stat, t.timeit(number=1000))
    except:
        t.print_exc()

La sortie est :

$ ./test_get.py
Time for for i in xrange(1000): iter(s).next()   :       0.433080
Time for for i in xrange(1000):
        for x in s:
                break:   0.148695
Time for for i in xrange(1000): s.add(s.pop())   :       0.317418
Time for for i in xrange(1000): s.get()          :       0.146673

Cela signifie que le pour/briser est la solution la plus rapide (parfois plus rapide que la solution personnalisée get()).

0 votes

Quelqu'un a-t-il une idée de la raison pour laquelle iter(s).next() est tellement plus lent que les autres possibilités, même plus lent que s.add(s.pop()) ? Pour moi, cela ressemble à une très mauvaise conception de iter() et next() si les temps sont comme ça.

0 votes

D'abord, cette ligne crée un nouvel objet iter à chaque itération.

3 votes

@Ryan : un objet itérateur n'est-il pas créé implicitement pour for x in s également ? " Un itérateur est créé pour le résultat de l'action expression_list ."

29voto

dF. Points 29787

Puisque vous voulez un élément aléatoire, cela fonctionnera également :

>>> import random
>>> s = set([1,2,3])
>>> random.sample(s, 1)
[2]

La documentation ne semble pas mentionner les performances de random.sample . D'après un test empirique très rapide avec une énorme liste et un énorme ensemble, il semble que le temps soit constant pour une liste mais pas pour l'ensemble. De plus, l'itération sur un ensemble n'est pas aléatoire ; l'ordre est indéfini mais prévisible :

>>> list(set(range(10))) == range(10)
True 

Si le caractère aléatoire est important et que vous avez besoin d'un tas d'éléments en temps constant (grands ensembles), j'utiliserais random.sample et convertir en liste d'abord :

>>> lst = list(s) # once, O(len(s))?
...
>>> e = random.sample(lst, 1)[0] # constant time

14 votes

Si vous ne voulez qu'un seul élément, random.choice est plus judicieux.

0 votes

List(s).pop() fera l'affaire si vous ne vous souciez pas de l'élément à prendre.

10 votes

@Gregg : Vous ne pouvez pas utiliser choice() car Python va essayer d'indexer votre jeu et ça ne marche pas.

6voto

Nick Points 3115

J'utilise une fonction utilitaire que j'ai écrite. Son nom est quelque peu trompeur car il laisse entendre qu'il pourrait s'agir d'un élément aléatoire ou quelque chose du genre.

def anyitem(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None

7 votes

Vous pouvez également utiliser next(iter(iterable), None) pour économiser de l'encre :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X