Python ha un set ordinato?


477

Python ha un dizionario ordinato . Che dire di un set ordinato?


18
che dire del contrario, un sacco di cose? (non ordinato e non unico)
wim

19
@wim collections.Counterè la borsa di Python.
terremoto del

1
Cosa succede se qualcosa viene aggiunto due volte? Quale dovrebbe essere la posizione?
McKay,

2
@McKay - se dovesse seguire il comportamento delle collezioni.OrderDict sarebbe ancora nella posizione dell'aggiunta iniziale
wojtow,

Risposte:


206

C'è una ricetta ordinata (possibile nuovo collegamento ) per questo, a cui si fa riferimento dalla documentazione di Python 2 . Funziona su Py2.6 o successivo e 3.0 o successivo senza alcuna modifica. L'interfaccia è quasi esattamente la stessa di un set normale, tranne per il fatto che l'inizializzazione dovrebbe essere fatta con un elenco.

OrderedSet([1, 2, 3])

Questo è un MutableSet, quindi la firma per .unionnon corrisponde a quella del set, ma poiché include __or__qualcosa di simile può essere facilmente aggiunta:

@staticmethod
def union(*sets):
    union = OrderedSet()
    union.union(*sets)
    return union

def union(self, *sets):
    for set in sets:
        self |= set

6
Ho selezionato la mia risposta perché il riferimento dalla documentazione rende così vicino a una risposta ufficiale
Casebash

49
L'interfaccia non è esattamente lo stesso come l'oggetto set normale, molti metodi essenziali mancano quali update, union, intersection.
xApple

5
Cordiali saluti, ho notato che una versione leggermente modificata della ricetta citata in questa risposta è stata aggiunta a PyPi come "set ordinato"
Geoffrey Hing

7
Sono abbastanza sicuro che non ti è permesso avere due metodi entrambi chiamati unionnella stessa classe. L'ultimo "vincerà" e il primo non esisterà in fase di esecuzione. Questo perché OrderedSet.union(senza parentesi) deve fare riferimento a un singolo oggetto.
Kevin,

3
Esiste anche un pacchetto "ordinatoset" che si basa sulla stessa ricetta ma implementato in Cython - pypi.python.org/pypi/orderedset .
mbdevpl,

149

Un set ordinato è funzionalmente un caso speciale di un dizionario ordinato.

Le chiavi di un dizionario sono uniche. Pertanto, se si ignorano i valori in un dizionario ordinato (ad es. Assegnandoli None), si ha essenzialmente un set ordinato.

A partire da Python 3.1 c'è collections.OrderedDict. Di seguito è riportata un'implementazione di esempio di un OrderedSet. (Si noti che solo pochi metodi devono essere definiti o ignorati: collections.OrderedDicted collections.MutableSeteseguire il sollevamento pesante.)

import collections

class OrderedSet(collections.OrderedDict, collections.MutableSet):

    def update(self, *args, **kwargs):
        if kwargs:
            raise TypeError("update() takes no keyword arguments")

        for s in args:
            for e in s:
                 self.add(e)

    def add(self, elem):
        self[elem] = None

    def discard(self, elem):
        self.pop(elem, None)

    def __le__(self, other):
        return all(e in other for e in self)

    def __lt__(self, other):
        return self <= other and self != other

    def __ge__(self, other):
        return all(e in self for e in other)

    def __gt__(self, other):
        return self >= other and self != other

    def __repr__(self):
        return 'OrderedSet([%s])' % (', '.join(map(repr, self.keys())))

    def __str__(self):
        return '{%s}' % (', '.join(map(repr, self.keys())))

    difference = __sub__ 
    difference_update = __isub__
    intersection = __and__
    intersection_update = __iand__
    issubset = __le__
    issuperset = __ge__
    symmetric_difference = __xor__
    symmetric_difference_update = __ixor__
    union = __or__

1
@Casebash: sì, uno potrebbe voler definire una classe OrderedSetche sottoclassi OrderedDicte abc.Setquindi definire __len__, __iter__e __contains__.
Stephan202,

1
@ Stephan202: Purtroppo, la collezione ABC vive collections, ma per il resto un buon suggerimento
u0b34a0f6ae

4
Questo è vero, ma di conseguenza hai molto spazio sprecato, il che porta a prestazioni non ottimali.
Daniel Kats,

3
Un'aggiunta; collections.OrderedDict è disponibile anche in Python 2.7.
Nurbldoff,

2
Fare OrderedSet([1,2,3])genera un errore di tipo. Come funziona il costruttore? Esempio di utilizzo mancante.
xApple

90

La risposta è no, ma puoi usare collections.OrderedDictdalla libreria standard di Python con solo chiavi (e valori come None) per lo stesso scopo.

Aggiornamento : A partire da Python 3.7 (e CPython 3.6), lo standard dictè garantito per preservare l'ordine ed è più performante di OrderedDict. (Per compatibilità con le versioni precedenti e soprattutto leggibilità, tuttavia, potresti voler continuare a utilizzare OrderedDict.)

Ecco un esempio di come utilizzare dictun set ordinato per filtrare gli articoli duplicati preservando l'ordine, emulando così un set ordinato. Usa il dictmetodo class fromkeys()per creare un dict, quindi chiedi semplicemente il keys()retro.

>>> keywords = ['foo', 'bar', 'bar', 'foo', 'baz', 'foo']

>>> list(dict.fromkeys(keywords))
['foo', 'bar', 'baz']

4
Forse vale la pena ricordare che questo funziona anche (più velocemente) con la vaniglia dict.fromkeys(). Ma in quel caso, l'ordine delle chiavi viene preservato solo nelle implementazioni di CPython 3.6+, quindi OrderedDictè una soluzione più portatile quando l'ordine conta.
jez

1
non funzionerà se i valori non sono stringa
Anwar Hossain,

4
@AnwarHossain keys = (1,2,3,1,2,1) list(OrderedDict.fromkeys(keys).keys())-> [1, 2, 3], python-3.7. Funziona.
raratiru,

1
Possiamo dedurre che anche Set in Python 3.7+ conserva l'ordine?
user474491

2
@ user474491 Diversamente dict, setin Python 3.7+ purtroppo non si conserva l'ordine.
cz

39

Posso farti meglio di un OrderedSet: boltons ha un Python puro, compatibile 2/3IndexedSet tipo che non è solo un insieme ordinato, ma supporta anche l'indicizzazione (come con le liste).

Semplicemente pip install boltons(o copia setutils.pynel tuo codebase), importa il IndexedSete:

>>> from boltons.setutils import IndexedSet
>>> x = IndexedSet(list(range(4)) + list(range(8)))
>>> x
IndexedSet([0, 1, 2, 3, 4, 5, 6, 7])
>>> x - set(range(2))
IndexedSet([2, 3, 4, 5, 6, 7])
>>> x[-1]
7
>>> fcr = IndexedSet('freecreditreport.com')
>>> ''.join(fcr[:fcr.index('.')])
'frecditpo'

Tutto è unico e mantenuto in ordine. Divulgazione completa: ho scritto il IndexedSet, ma ciò significa anche che puoi darmi bug in caso di problemi . :)


39

Implementazioni su PyPI

Mentre altri hanno sottolineato che non esiste un'implementazione integrata di un set di conservazione degli ordini di inserzione in Python (ancora), sento che a questa domanda manca una risposta che afferma cosa si può trovare su PyPI .

Ci sono i pacchetti:

Alcune di queste implementazioni si basano sulla ricetta pubblicata da Raymond Hettinger su ActiveState che è menzionata anche in altre risposte qui.

Alcune differenze

  • ordinato-set (versione 1.1)
    • vantaggio: O (1) per ricerche per indice (ad es. my_set[5])
  • oset (versione 0.1.3)
    • vantaggio: O (1) per remove(item)
    • svantaggio: apparentemente O (n) per ricerche per indice

Entrambe le implementazioni hanno O (1) per add(item)e __contains__(item)( item in my_set).


2
Un nuovo contendente è collections_extended.setlist . Funzioni come set.unionnon ci lavorano, anche se eredita collections.abc.Set.
timdiels

3
OrderedSetora supportaremove
warvariuc il

17

Se stai usando il set ordinato per mantenere un ordine ordinato, considera l'utilizzo di un'implementazione del set ordinato da PyPI. Il modulo sortcontainers fornisce un SortedSet proprio per questo scopo. Alcuni vantaggi: pure-Python, implementazioni fast-as-C, copertura del test unitario al 100%, ore di stress test.

L'installazione da PyPI è semplice con pip:

pip install sortedcontainers

Si noti che se non è possibile pip install, è sufficiente estrarre i file sortlist.py e sortset.py dal repository open source .

Una volta installato puoi semplicemente:

from sortedcontainers import SortedSet
help(SortedSet)

Il modulo dei contenitori ordinati mantiene anche un confronto delle prestazioni con diverse implementazioni alternative.

Per il commento che ha chiesto del tipo di dati bag di Python, c'è in alternativa un tipo di dati SortedList che può essere utilizzato per implementare in modo efficiente un bag.


Nota che la SortedSetclasse lì richiede che i membri siano comparabili e hash.
gsnedders,

4
@gsnedders I builtin sete frozensetrichiedono anche elementi per essere hash. Il vincolo comparabile è l'aggiunta per SortedSet, ma è anche un vincolo ovvio.
gotgenes,

2
Come suggerisce il nome, questo non mantiene l'ordine. Non è altro che ordinato (set ([sequenza])) che rende migliore?
Due

@ldmtwo Non sono sicuro a cui ti riferisci, ma solo per essere chiari, SortedSet come parte di Container ordinati mantiene l'ordine ordinato.
Concedi il

2
@GrantJ - È la differenza tra se mantiene l' ordine di inserimento o l' ordinamento . La maggior parte delle altre risposte riguarda l'ordine di inserimento. Penso che tu sia già a conoscenza di questo sulla base della tua prima frase, ma è probabilmente ciò che ldmtwo sta dicendo.
Giustino,

9

Nel caso in cui stai già utilizzando i panda nel tuo codice, il suo Indexoggetto si comporta in modo simile a un set ordinato, come mostrato in questo articolo .

Esempi dall'articolo:

indA = pd.Index([1, 3, 5, 7, 9])
indB = pd.Index([2, 3, 5, 7, 11])

indA & indB  # intersection
indA | indB  # union
indA - indB  # difference
indA ^ indB  # symmetric difference

Puoi includere un esempio in questa risposta? I collegamenti tendono ad essere interrotti dopo qualche tempo.
Alechan,

1
per la differenza tra i set, in realtà è necessario utilizzare indA.difference(indB), il segno meno esegue la sottrazione standard
gg349,

7

Un po 'in ritardo al gioco, ma ho scritto una classe setlistcome parte di collections-extendedciò che implementa completamente sia SequenceeSet

>>> from collections_extended import setlist
>>> sl = setlist('abracadabra')
>>> sl
setlist(('a', 'b', 'r', 'c', 'd'))
>>> sl[3]
'c'
>>> sl[-1]
'd'
>>> 'r' in sl  # testing for inclusion is fast
True
>>> sl.index('d')  # so is finding the index of an element
4
>>> sl.insert(1, 'd')  # inserting an element already in raises a ValueError
ValueError
>>> sl.index('d')
4

GitHub: https://github.com/mlenzen/collections-extended

Documentazione: http://collections-extended.lenzm.net/en/latest/

PyPI: https://pypi.python.org/pypi/collections-extended


7

Non ce n'è OrderedSetnella biblioteca ufficiale. Faccio un completo cheatsheet di tutta la struttura dei dati per riferimento.

DataStructure = {
    'Collections': {
        'Map': [
            ('dict', 'OrderDict', 'defaultdict'),
            ('chainmap', 'types.MappingProxyType')
        ],
        'Set': [('set', 'frozenset'), {'multiset': 'collection.Counter'}]
    },
    'Sequence': {
        'Basic': ['list', 'tuple', 'iterator']
    },
    'Algorithm': {
        'Priority': ['heapq', 'queue.PriorityQueue'],
        'Queue': ['queue.Queue', 'multiprocessing.Queue'],
        'Stack': ['collection.deque', 'queue.LifeQueue']
        },
    'text_sequence': ['str', 'byte', 'bytearray']
}

3

Il pacchetto ParallelRegression fornisce una classe set ordinata setList () che è più completa di metodo rispetto alle opzioni basate sulla ricetta ActiveState. Supporta tutti i metodi disponibili per gli elenchi e la maggior parte se non tutti i metodi disponibili per gli insiemi.


2

Come menzionano altre risposte, come per Python 3.7+, il dict è ordinato per definizione. Invece di sottoclasse OrderedDictpossiamo sottoclassare abc.collections.MutableSeto typing.MutableSetusare le chiavi del dict per memorizzare i nostri valori.

class OrderedSet(typing.MutableSet[T]):
    """A set that preserves insertion order by internally using a dict."""

    def __init__(self, iterable: t.Iterator[T]):
        self._d = dict.fromkeys(iterable)

    def add(self, x: T) -> None:
        self._d[x] = None

    def discard(self, x: T) -> None:
        self._d.pop(x)

    def __contains__(self, x: object) -> bool:
        return self._d.__contains__(x)

    def __len__(self) -> int:
        return self._d.__len__()

    def __iter__(self) -> t.Iterator[T]:
        return self._d.__iter__()

Quindi solo:

x = OrderedSet([1, 2, -1, "bar"])
x.add(0)
assert list(x) == [1, 2, -1, "bar", 0]

Ho inserito questo codice in una piccola libreria , quindi chiunque può pip installfarlo.


-4

Per molti scopi sarà sufficiente semplicemente chiamare ordinati. Per esempio

>>> s = set([0, 1, 2, 99, 4, 40, 3, 20, 24, 100, 60])
>>> sorted(s)
[0, 1, 2, 3, 4, 20, 24, 40, 60, 99, 100]

Se lo utilizzerai ripetutamente, si verificherà un sovraccarico chiamando la funzione ordinata, quindi potresti voler salvare l'elenco risultante, a condizione che tu abbia finito di cambiare il set. Se è necessario mantenere elementi unici e ordinati, sono d'accordo con il suggerimento di utilizzare OrderedDict da raccolte con un valore arbitrario come Nessuno.


43
Lo scopo di OrderedSet è quello di essere in grado di ottenere gli articoli nell'ordine in cui sono stati aggiunti al set. Un esempio potrebbe essere chiamato SortedSet ...
Manutenzione periodica

-4

Quindi avevo anche un piccolo elenco in cui avevo chiaramente la possibilità di introdurre valori non univoci.

Ho cercato l'esistenza di un elenco univoco di qualche tipo, ma poi ho capito che testare l'esistenza dell'elemento prima di aggiungerlo funziona bene.

if(not new_element in my_list):
    my_list.append(new_element)

Non so se ci sono avvertimenti in questo semplice approccio, ma risolve il mio problema.


Il problema principale con questo approccio è che l'aggiunta viene eseguita in O (n). Significa che diventa più lento con grandi liste. I set integrati di Python sono molto efficaci nel rendere più veloce l'aggiunta di elementi. Ma per semplici casi d'uso, sicuramente funziona!
Draconis,
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.