La conversione di un elenco in un insieme modifica l'ordine degli elementi


119

Recentemente ho notato che quando si converte lista setnell'ordine degli elementi viene modificato e viene ordinato per carattere.

Considera questo esempio:

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

Le mie domande sono:

  1. Perché sta succedendo?
  2. Come posso eseguire operazioni di impostazione (in particolare Imposta differenza) senza perdere l'ordine iniziale?

8
Perché non vuoi perdere l'ordine iniziale, soprattutto se stai eseguendo operazioni di set? "ordine" è un concetto privo di significato per gli insiemi, non solo in Python ma in matematica.
Karl Knechtel

131
@KarlKnechtel - Sì "l'ordine è un concetto senza senso per gli insiemi ... in matematica" ma ho problemi nel mondo reale :)
d.putto

Su CPython 3.6+ unique = list(dict.fromkeys([1, 2, 1]).keys()). Funziona perché dictora preserva l'ordine di inserzione.
Boris

Risposte:


106
  1. A setè una struttura dati non ordinata, quindi non preserva l'ordine di inserzione.

  2. Dipende dalle tue esigenze. Se si dispone di un elenco normale e si desidera rimuovere un insieme di elementi preservando l'ordine dell'elenco, è possibile farlo con una comprensione dell'elenco:

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

    Se hai bisogno di una struttura dati che supporti sia test rapidi di appartenenza che preservazione dell'ordine di inserzione , puoi utilizzare le chiavi di un dizionario Python, che a partire da Python 3.7 è garantito per preservare l'ordine di inserzione:

    >>> 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}

    bnon ha davvero bisogno di essere ordinato qui - potresti usare anche un file set. Tieni presente che a.keys() - b.keys()restituisce la differenza di set come a set, quindi non manterrà l'ordine di inserzione.

    Nelle versioni precedenti di Python, puoi usare collections.OrderedDictinvece:

    >>> 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
Nessun oggetto costa 16 byte. Se solo è presente un OrderedSet () predefinito. :(
Sean

2
@ Sean no, non lo fanno. Noneè una lingua singleton garantita. In CPython, a il costo effettivo è solo il puntatore (anche se quel costo è sempre lì, ma per un dict, puoi quasi considerare Nonee altri singleton o riferimenti condivisi "gratuiti"), quindi una parola macchina, probabilmente 8 byte sui computer moderni . Ma sì, non è efficiente in termini di spazio come potrebbe essere un set.
juanpa.arrivillaga

2
Su CPython 3.6+ puoi farlo dict.fromkeys([1, 2, 1]).keys()perché anche i normali dictmantengono l'ordine.
Boris

@Boris Questo è stato solo una parte della specifica del linguaggio a partire da Python 3.7. Anche se l'implementazione di CPython preserva già l'ordine di inserimento nella versione 3.6, questo è considerato un dettaglio di implementazione che potrebbe non essere seguito da altre implementazioni di Python.
Sven Marnach,

@Sven I said CPython. Lo posto ovunque, mi sto solo stancando di scrivere "CPython 3.6 o qualsiasi altra implementazione a partire da Python 3.7". Non importa nemmeno, tutti usano CPython
Boris

53

In Python 3.6, set()ora dovrebbe mantenere l'ordine, ma c'è un'altra soluzione per Python 2 e 3:

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

8
Due note sulla conservazione dell'ordine: solo a partire da Python 3.6, e anche lì, è considerato un dettaglio di implementazione, quindi non fare affidamento su di esso. A parte questo, il tuo codice è molto inefficiente perché ogni volta che x.indexviene chiamato, viene eseguita una ricerca lineare. Se stai bene con la complessità quadratica, non c'è motivo di usare a setin primo luogo.
Thijs van Dien

27
@ThijsvanDien Questo è sbagliato, set()non è ordinato in Python 3.6, nemmeno come dettaglio di implementazione, stai pensando a dicts
Chris_Rands

8
@ThijsvanDien No non stanno allineati, anche se a volte appare così perché ints spesso hash a se stessi stackoverflow.com/questions/45581901/...~~V~~plural~~3rd
Chris_Rands

3
Prova a x=[1,2,-1,20,6,210]renderlo un set. Vedrai che non è affatto ordinato, testato in Python 3.6.
GabrielChu

3
Non riesco a capire perché questa risposta abbia così tanti voti positivi, non mantiene l'ordine di inserzione, né restituisce un set.
Igor Rodriguez

20

Rispondendo alla tua prima domanda, un set è una struttura dati ottimizzata per le operazioni di set. Come un insieme matematico, non impone né mantiene alcun ordine particolare degli elementi. Il concetto astratto di un insieme non impone l'ordine, quindi l'implementazione non è richiesta. Quando crei un set da un elenco, Python ha la libertà di modificare l'ordine degli elementi per le esigenze dell'implementazione interna che utilizza per un set, che è in grado di eseguire operazioni di set in modo efficiente.


9

rimuovere i duplicati e preservare l'ordine tramite la funzione sottostante

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

controlla questo link


Bello, molto meglio della mia soluzione :)
Tiger-222

8

In matematica, ci sono insiemi e insiemi ordinati (osets).

  • set : un contenitore non ordinato di elementi unici (implementato)
  • oset : un contenitore ordinato di elementi unici (NotImplemented)

In Python, solo i set vengono implementati direttamente. Possiamo emulare oset con i normali tasti dict ( 3.7+ ).

Dato

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

Codice

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

dimostrazione

I replicati vengono rimossi, l'ordine di inserzione viene mantenuto.

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

Operazioni di tipo set sui tasti Dict.

oset - b
# {1, 20, 210}

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

oset & b
# {2, 6}

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

Dettagli

Nota: una struttura non ordinata non preclude gli elementi ordinati. Piuttosto, l'ordine mantenuto non è garantito. Esempio:

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

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

Si può essere contenti di scoprire che una lista e un multiset (mset) sono due strutture di dati matematiche più affascinanti:

  • list : un contenitore ordinato di elementi che consente repliche (implementato)
  • mset : un contenitore non ordinato di elementi che consente repliche (NotImplemented) *

Sommario

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

* Un multiset può essere emulato indirettamente con collections.Counter(), una mappatura dict-like delle molteplicità (conteggi).


4

Come indicato in altre risposte, gli insiemi sono strutture di dati (e concetti matematici) che non conservano l'ordine degli elementi -

Tuttavia, utilizzando una combinazione di set e dizionari, è possibile ottenere tutto ciò che desideri: prova a utilizzare questi frammenti:

# 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

Basandomi sulla risposta di Sven, ho scoperto che l'utilizzo di collections.OrderedDict in questo modo mi ha aiutato a realizzare ciò che desideri e mi ha permesso di aggiungere più elementi al 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)])

Se vuoi aggiungere elementi ma trattalo comunque come un set puoi semplicemente fare:

z['nextitem']=None

E puoi eseguire un'operazione come z.keys () sul dict e ottenere il set:

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

devi fare list(z.keys())per ottenere l'output della lista.
jxn

in Python 3, sì. non in Python 2, anche se avrei dovuto specificarlo.
jimh

0

Un'implementazione del concetto di punteggio più alto sopra che lo riporta a un elenco:

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

Testato (brevemente) su Python 3.6 e Python 2.7.


0

Nel caso in cui tu abbia un numero limitato di elementi nei tuoi due elenchi iniziali su cui desideri eseguire un'operazione di impostazione della differenza, invece di utilizzare il collections.OrderedDictche complica l'implementazione e lo rende meno leggibile, puoi utilizzare:

# 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]

La sua complessità temporale non è così buona, ma è chiara e facile da leggere.


0

È interessante che le persone usino sempre il "problema del mondo reale" per scherzare sulla definizione nella scienza teorica.

Se il set ha un ordine, devi prima capire i seguenti problemi. Se il tuo elenco contiene elementi duplicati, quale dovrebbe essere l'ordine quando lo trasformi in un set? Qual è l'ordine se uniamo due insiemi? Qual è l'ordine se intersechiamo due insiemi con ordine diverso sugli stessi elementi?

Inoltre, set è molto più veloce nella ricerca di una particolare chiave che è molto buona nel funzionamento dei set (ed è per questo che è necessario un set, ma non un elenco).

Se ti interessa davvero l'indice, tienilo come un elenco. Se si desidera comunque eseguire un'operazione di impostazione sugli elementi in molti elenchi, il modo più semplice è creare un dizionario per ogni elenco con le stesse chiavi nell'insieme insieme a un valore di elenco contenente tutto l'indice della chiave nell'elenco originale.

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}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.