La conversion d'une liste en un ensemble modifie l'ordre des éléments


119

Récemment, j'ai remarqué que lorsque je convertis un listen setordre des éléments, il est modifié et trié par caractère.

Prenons cet exemple:

x=[1,2,20,6,210]
print x 
# [1, 2, 20, 6, 210] # the order is same as initial order

set(x)
# set([1, 2, 20, 210, 6]) # in the set(x) output order is sorted

Mes questions sont -

  1. Pourquoi cela arrive-t-il?
  2. Comment puis-je effectuer des opérations de définition (en particulier Définir la différence) sans perdre la commande initiale?

8
Pourquoi ne voulez-vous pas perdre la commande initiale, surtout si vous effectuez des opérations de set? «ordre» est un concept dénué de sens pour les ensembles, non seulement en Python mais en mathématiques.
Karl Knechtel

131
@KarlKnechtel - Oui "l'ordre est un concept sans signification pour les ensembles ... en mathématiques" mais j'ai des problèmes du monde réel :)
d.putto

Sur CPython 3.6+ unique = list(dict.fromkeys([1, 2, 1]).keys()). Cela fonctionne parce que l' dictordre d'insertion est conservé maintenant.
Boris

Réponses:


106
  1. A setest une structure de données non ordonnée, elle ne conserve donc pas l'ordre d'insertion.

  2. Cela dépend de vos besoins. Si vous avez une liste normale et que vous souhaitez supprimer un ensemble d'éléments tout en préservant l'ordre de la liste, vous pouvez le faire avec une compréhension de liste:

    >>> a = [1, 2, 20, 6, 210]
    >>> b = set([6, 20, 1])
    >>> [x for x in a if x not in b]
    [2, 210]

    Si vous avez besoin d'une structure de données qui prend en charge à la fois les tests d'appartenance rapides et la préservation de l'ordre d'insertion , vous pouvez utiliser les clés d'un dictionnaire Python, qui à partir de Python 3.7 est garanti pour préserver l'ordre d'insertion:

    >>> a = dict.fromkeys([1, 2, 20, 6, 210])
    >>> b = dict.fromkeys([6, 20, 1])
    >>> dict.fromkeys(x for x in a if x not in b)
    {2: None, 210: None}

    bn'a pas vraiment besoin d'être commandé ici - vous pouvez également utiliser un set. Notez que a.keys() - b.keys()renvoie la différence définie sous forme de a set, donc il ne conservera pas l'ordre d'insertion.

    Dans les anciennes versions de Python, vous pouvez utiliser à la collections.OrderedDictplace:

    >>> a = collections.OrderedDict.fromkeys([1, 2, 20, 6, 210])
    >>> b = collections.OrderedDict.fromkeys([6, 20, 1])
    >>> collections.OrderedDict.fromkeys(x for x in a if x not in b)
    OrderedDict([(2, None), (210, None)])

3
Aucun objet ne coûte 16 octets. Si seulement il y a un OrderedSet () par défaut. :(
Sean

2
@Sean non, ils ne le font pas. Noneest une langue singleton garantie. En CPython, le coût réel n'est que le pointeur (bien que ce coût soit toujours là, mais pour un dict, vous pouvez presque considérer Noneet d'autres singletons ou références partagées "libres"), donc un mot machine, probablement 8 octets sur les ordinateurs modernes . Mais oui, ce n'est pas aussi peu encombrant qu'un ensemble pourrait l'être.
juanpa.arrivillaga

2
Sur CPython 3.6+, vous pouvez simplement le faire dict.fromkeys([1, 2, 1]).keys()parce que les réguliers dictconservent également l'ordre.
Boris

@Boris Cela ne faisait partie que de la spécification du langage à partir de Python 3.7. Bien que l'implémentation CPython préserve déjà l'ordre d'insertion dans la version 3.6, cela est considéré comme un détail d'implémentation qui peut ne pas être suivi par d'autres implémentations Python.
Sven Marnach

@Sven j'ai dit CPython. Je poste ça partout, j'en ai juste assez d'écrire "CPython 3.6 ou toute autre implémentation commençant par Python 3.7". Peu importe, tout le monde utilise CPython
Boris

53

Dans Python 3.6, set()maintenant devrait garder l'ordre, mais il existe une autre solution pour Python 2 et 3:

>>> x = [1, 2, 20, 6, 210]
>>> sorted(set(x), key=x.index)
[1, 2, 20, 6, 210]

8
Deux remarques concernant la préservation de l'ordre: seulement à partir de Python 3.6, et même là, c'est considéré comme un détail d'implémentation, alors ne vous y fiez pas. En dehors de cela, votre code est très inefficace car à chaque x.indexappel, une recherche linéaire est effectuée. Si vous êtes d'accord avec la complexité quadratique, il n'y a aucune raison d'utiliser a seten premier lieu.
Thijs van Dien

27
@ThijsvanDien C'est faux, set()n'est pas commandé dans Python 3.6, même pas comme un détail d'implémentation, vous pensez à dicts
Chris_Rands

8
@ThijsvanDien Non, ils ne sont pas triés, bien qu'ils apparaissent parfois ainsi parce qu'ils intse hachent souvent eux-mêmes stackoverflow.com/questions/45581901
...

3
Essayez d'en x=[1,2,-1,20,6,210]faire un ensemble. Vous verrez qu'il n'est pas du tout commandé, testé en Python 3.6.
GabrielChu

3
Je ne peux pas comprendre pourquoi cette réponse a autant de votes positifs, elle ne garde pas l'ordre d'insertion, ni ne renvoie un ensemble.
Igor Rodriguez

20

En réponse à votre première question, un ensemble est une structure de données optimisée pour les opérations d'ensemble. Comme un ensemble mathématique, il n'applique ni ne maintient aucun ordre particulier des éléments. Le concept abstrait d'un ensemble n'applique pas l'ordre, donc l'implémentation n'est pas obligée de le faire. Lorsque vous créez un ensemble à partir d'une liste, Python a la liberté de modifier l'ordre des éléments pour les besoins de l'implémentation interne qu'il utilise pour un ensemble, qui est capable d'effectuer des opérations d'ensemble efficacement.


9

supprimer les doublons et conserver l'ordre par la fonction ci-dessous

def unique(sequence):
    seen = set()
    return [x for x in sequence if not (x in seen or seen.add(x))]

vérifier ce lien


Belle, bien meilleure que ma solution :)
Tiger-222

8

En mathématiques, il existe des ensembles et des ensembles ordonnés (osets).

  • set : un conteneur non ordonné d'éléments uniques (implémenté)
  • oset : un conteneur ordonné d'éléments uniques (NotImplemented)

En Python, seuls les ensembles sont directement implémentés. Nous pouvons émuler des osets avec des clés dict régulières ( 3.7+ ).

Donné

a = [1, 2, 20, 6, 210, 2, 1]
b = {2, 6}

Code

oset = dict.fromkeys(a).keys()
# dict_keys([1, 2, 20, 6, 210])

Démo

Les répliques sont supprimées, l'ordre d'insertion est conservé.

list(oset)
# [1, 2, 20, 6, 210]

Opérations de type set sur les clés dict.

oset - b
# {1, 20, 210}

oset | b
# {1, 2, 5, 6, 20, 210}

oset & b
# {2, 6}

oset ^ b
# {1, 5, 20, 210}

Détails

Remarque: une structure non ordonnée n'empêche pas les éléments ordonnés. Au contraire, le maintien de l'ordre n'est pas garanti. Exemple:

assert {1, 2, 3} == {2, 3, 1}                    # sets (order is ignored)

assert [1, 2, 3] != [2, 3, 1]                    # lists (order is guaranteed)

On peut être heureux de découvrir qu'une liste et un multiset (mset) sont deux structures de données mathématiques plus fascinantes:

  • list : un conteneur ordonné d'éléments qui permet les répliques (implémenté)
  • mset : un conteneur d'éléments non ordonnés qui permet les répliques (NotImplemented) *

Résumé

Container | Ordered | Unique | Implemented
----------|---------|--------|------------
set       |    n    |    y   |     y
oset      |    y    |    y   |     n
list      |    y    |    n   |     y
mset      |    n    |    n   |     n*  

* Un multiset peut être indirectement émulé avec collections.Counter(), un mappage de type dict de multiplicités (comptes).


4

Comme indiqué dans d'autres réponses, les ensembles sont des structures de données (et des concepts mathématiques) qui ne préservent pas l'ordre des éléments -

Cependant, en utilisant une combinaison d'ensembles et de dictionnaires, il est possible que vous puissiez obtenir ce que vous voulez - essayez d'utiliser ces extraits:

# save the element order in a dict:
x_dict = dict(x,y for y, x in enumerate(my_list) )
x_set = set(my_list)
#perform desired set operations
...
#retrieve ordered list from the set:
new_list = [None] * len(new_set)
for element in new_set:
   new_list[x_dict[element]] = element

1

En me basant sur la réponse de Sven, j'ai trouvé en utilisant des collections.OrderedDict m'a aidé à accomplir ce que vous voulez et m'a permis d'ajouter plus d'éléments au dict:

import collections

x=[1,2,20,6,210]
z=collections.OrderedDict.fromkeys(x)
z
OrderedDict([(1, None), (2, None), (20, None), (6, None), (210, None)])

Si vous souhaitez ajouter des éléments tout en les traitant comme un ensemble, vous pouvez simplement faire:

z['nextitem']=None

Et vous pouvez effectuer une opération comme z.keys () sur le dict et obtenir l'ensemble:

z.keys()
[1, 2, 20, 6, 210]

vous devez faire list(z.keys())pour obtenir la sortie de la liste.
jxn

en Python 3, oui. pas dans Python 2, même si j'aurais dû le spécifier.
jimh

0

Une implémentation du concept de score le plus élevé ci-dessus qui le ramène à une liste:

def SetOfListInOrder(incominglist):
    from collections import OrderedDict
    outtemp = OrderedDict()
    for item in incominglist:
        outtemp[item] = None
    return(list(outtemp))

Testé (brièvement) sur Python 3.6 et Python 2.7.


0

Dans le cas où vous avez un petit nombre d'éléments dans vos deux listes initiales sur lesquelles vous souhaitez effectuer une opération de différence de définition, au lieu d'utiliser collections.OrderedDictce qui complique l'implémentation et la rend moins lisible, vous pouvez utiliser:

# initial lists on which you want to do set difference
>>> nums = [1,2,2,3,3,4,4,5]
>>> evens = [2,4,4,6]
>>> evens_set = set(evens)
>>> result = []
>>> for n in nums:
...   if not n in evens_set and not n in result:
...     result.append(n)
... 
>>> result
[1, 3, 5]

Sa complexité temporelle n'est pas si bonne mais elle est soignée et facile à lire.


0

Il est intéressant de noter que les gens utilisent toujours le «problème du monde réel» pour plaisanter sur la définition en science théorique.

Si l'ensemble a de l'ordre, vous devez d'abord résoudre les problèmes suivants. Si votre liste contient des éléments en double, quel devrait être l'ordre lorsque vous en faites un ensemble? Quel est l'ordre si nous réunissons deux ensembles? Quel est l'ordre si nous croisons deux ensembles avec un ordre différent sur les mêmes éléments?

De plus, set est beaucoup plus rapide dans la recherche d'une clé particulière, ce qui est très bon pour le fonctionnement des ensembles (et c'est pourquoi vous avez besoin d'un ensemble, mais pas d'une liste).

Si vous vous souciez vraiment de l'index, conservez-le simplement sous forme de liste. Si vous souhaitez toujours effectuer une opération d'ensemble sur les éléments de nombreuses listes, le moyen le plus simple consiste à créer un dictionnaire pour chaque liste avec les mêmes clés dans l'ensemble avec une valeur de liste contenant tous les index de la clé dans la liste d'origine.

def indx_dic(l):
    dic = {}
    for i in range(len(l)):
        if l[i] in dic:
            dic.get(l[i]).append(i)
        else:
            dic[l[i]] = [i]
    return(dic)

a = [1,2,3,4,5,1,3,2]
set_a  = set(a)
dic_a = indx_dic(a)

print(dic_a)
# {1: [0, 5], 2: [1, 7], 3: [2, 6], 4: [3], 5: [4]}
print(set_a)
# {1, 2, 3, 4, 5}

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.