O Python tem um conjunto ordenado?

477

Python tem um dicionário ordenado . Que tal um conjunto encomendado?

Casebash
fonte
18
e o inverso, um saco de coisas? (desordenada e não exclusiva)
wim
19
@wim collections.Counteré o saco do Python.
Flornquake 15/09/13
1
E se algo for adicionado duas vezes? Qual deve ser a posição?
26618 McKay
2
@McKay - se fosse para seguir o comportamento de collections.OrderDict ele ainda estaria na posição da adição inicial
wojtow

Respostas:

206

Existe uma receita de conjunto ordenado (possível novo link ) para isso, que é referida na documentação do Python 2 . Isso é executado no Py2.6 ou posterior e 3.0 ou posterior sem nenhuma modificação. A interface é quase exatamente igual a um conjunto normal, exceto que a inicialização deve ser feita com uma lista.

OrderedSet([1, 2, 3])

Como é um MutableSet, a assinatura para .unionnão corresponde à do conjunto, mas como inclui __or__algo semelhante, pode ser facilmente adicionada:

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

def union(self, *sets):
    for set in sets:
        self |= set
Casebash
fonte
6
Eu selecionei minha própria resposta porque a referência da documentação faz com que isso se aproxime de uma resposta oficial #
1010 Casebash
49
A interface não é exatamente o mesmo que o objeto do conjunto normal, muitos métodos essenciais estão faltando, como update, union, intersection.
XApple
5
FYI, eu notei que uma versão ligeiramente modificada da receita citada nesta resposta foi adicionado ao PyPI como "ordenou-set"
Geoffrey Hing
7
Tenho certeza que você não tem permissão para ter dois métodos, ambos chamados unionna mesma classe. O último "vencerá" e o primeiro deixará de existir em tempo de execução. Isso ocorre porque OrderedSet.union(sem parênteses) deve se referir a um único objeto.
Kevin
3
Há também o pacote "orderedset", baseado na mesma receita, mas implementado no Cython - pypi.python.org/pypi/orderedset .
Mdevpl 31/08
149

Um conjunto ordenado é funcionalmente um caso especial de um dicionário ordenado.

As chaves de um dicionário são únicas. Assim, se alguém desconsidera os valores em um dicionário ordenado (por exemplo, atribuindo-os None), então um possui essencialmente um conjunto ordenado.

A partir do Python 3.1, existe collections.OrderedDict. A seguir, é apresentado um exemplo de implementação de um OrderedSet. (Observe que apenas alguns métodos precisam ser definidos ou substituídos: collections.OrderedDicte collections.MutableSetfaça o trabalho pesado.)

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__
Stephan202
fonte
1
@Casebash: sim, pode-se querer definir uma classe OrderedSetque subclasses OrderedDicte abc.Setem seguida, definir __len__, __iter__e __contains__.
31410 Stephanie
1
@ Stephan202: Lamentavelmente, o ABC coleta viver em collections, mas caso contrário uma boa sugestão
u0b34a0f6ae
4
Isso é verdade, mas você tem muito espaço desperdiçado como resultado, o que leva a um desempenho abaixo do ideal.
quer
3
Uma adição; collections.OrderedDict também está disponível no python 2.7.
Nurbldoff
2
Fazer OrderedSet([1,2,3])gera um TypeError. Como o construtor funciona? Exemplo de uso ausente.
XApple
90

A resposta é não, mas você pode usar collections.OrderedDictda biblioteca padrão do Python apenas com chaves (e valores como None) para o mesmo objetivo.

Atualização : A partir do Python 3.7 (e CPython 3.6), o padrão dicté garantido para preservar a ordem e tem mais desempenho do que OrderedDict. (Para compatibilidade com versões anteriores e, especialmente, legibilidade, no entanto, convém continuar usando OrderedDict.)

Aqui está um exemplo de como usar dictcomo um conjunto ordenado para filtrar itens duplicados enquanto preserva a ordem, emulando um conjunto ordenado. Use o dictmétodo de classe fromkeys()para criar um ditado e, em seguida, simplesmente peça as keys()costas.

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

>>> list(dict.fromkeys(keywords))
['foo', 'bar', 'baz']
jrc
fonte
4
Talvez valha a pena mencionar que isso também funciona (mais rápido) com baunilha dict.fromkeys(). Mas, nesse caso, o pedido de chave é preservado apenas nas implementações do CPython 3.6+, portanto, OrderedDicté uma solução mais portátil quando o pedido é importante.
Jez
1
não vai funcionar se os valores não são corda
Anwar Hossain
4
@AnwarHossain keys = (1,2,3,1,2,1) list(OrderedDict.fromkeys(keys).keys())-> [1, 2, 3], python-3.7. Funciona.
raratiru 9/04/19
1
Podemos inferir que o Set in Python 3.7+ também preserva a ordem?
user474491
2
@ user474491 Ao contrário dict, setno Python 3.7+, infelizmente, não preserva a ordem.
cz
39

Eu posso fazer melhor do que um OrderedSet: Bolton tem um puro-Python, 03/02 compatível IndexedSettipo que não é apenas um conjunto ordenado, mas também suporta indexação (como com listas).

Simplesmente pip install boltons(ou copie setutils.pypara a sua base de código), importe o 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'

Tudo é único e mantido em ordem. Divulgação completa: eu escrevi o IndexedSet, mas isso também significa que você pode me incomodar se houver algum problema . :)

Mahmoud Hashemi
fonte
39

Implementações no PyPI

Enquanto outros apontaram que ainda não existe uma implementação embutida de um conjunto de preservação de ordem de inserção no Python, sinto que essa pergunta está faltando uma resposta que indica o que pode ser encontrado no PyPI .

Existem os pacotes:

Algumas dessas implementações são baseadas na receita postada por Raymond Hettinger no ActiveState, que também é mencionada em outras respostas aqui.

Algumas diferenças

  • conjunto ordenado (versão 1.1)
    • vantagem: O (1) para pesquisas por índice (por exemplo my_set[5])
  • oset (versão 0.1.3)
    • vantagem: O (1) para remove(item)
    • desvantagem: aparentemente O (n) para pesquisas por índice

Ambas as implementações têm O (1) para add(item)e __contains__(item)( item in my_set).

Daniel K
fonte
2
Um novo concorrente é collections_extended.setlist . Funções como set.unionnão funcionam, apesar de herdar collections.abc.Set.
timdiels 16/03/16
3
OrderedSetagora suportaremove
warvariuc
17

Se você estiver usando o conjunto ordenado para manter uma ordem classificada, considere usar uma implementação de conjunto classificado do PyPI. O módulo de contêineres classificados fornece um SortedSet apenas para essa finalidade. Alguns benefícios: implementações de Python puro, fast-as-C, 100% de cobertura de teste de unidade, horas de teste de estresse.

A instalação do PyPI é fácil com o pip:

pip install sortedcontainers

Observe que, se não puder pip install, basta puxar os arquivos sorted.py e sorted.set.py do repositório de código-fonte aberto .

Uma vez instalado, você pode simplesmente:

from sortedcontainers import SortedSet
help(SortedSet)

O módulo de contêineres classificados também mantém uma comparação de desempenho com várias implementações alternativas.

Para o comentário que foi perguntado sobre o tipo de dados da bolsa do Python, existe alternativamente um tipo de dados SortedList que pode ser usado para implementar com eficiência uma bolsa.

GrantJ
fonte
Observe que a SortedSetclasse requer que os membros sejam comparáveis ​​e hashable.
gsnedders
4
@gsnedders Os componentes internos sete frozensettambém exigem que os elementos sejam laváveis. A restrição comparável é a adição SortedSet, mas também é uma restrição óbvia.
gotgenes
2
Como o nome sugere, isso não mantém a ordem. Não é nada além de ordenado (conjunto ([sequência])) que melhora?
Ldmtwo 06/11/19
@ldmtwo Não sei ao que você está se referindo, mas, para ficar claro, SortedSet como parte de Containers classificados mantém a ordem classificada.
precisa
2
@GrantJ - É a diferença entre manter a ordem de inserção ou a ordem de classificação . A maioria das outras respostas refere-se ao pedido de inserção. Acho que você já está ciente disso com base na sua primeira frase, mas é provavelmente o que ldmtwo está dizendo.
11743 Justin
9

Caso você já esteja usando pandas em seu código, o Indexobjeto se comporta como um conjunto ordenado, conforme mostrado neste artigo .

Exemplos do artigo:

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
Berislav Lopac
fonte
Você pode incluir um exemplo nesta resposta? Os links tendem a ser quebrados após algum tempo.
Alechan 11/04
1
para a diferença entre conjuntos, você realmente precisa usar indA.difference(indB), o sinal de menos executa subtração padrão
gg349 28/04
7

Um pouco tarde para o jogo, mas eu escrevi uma classe setlistcomo parte do collections-extendedque totalmente implementa ambos 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

Documentação: http://collections-extended.lenzm.net/en/latest/

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

Michael Lenzen
fonte
7

Não há OrderedSetna biblioteca oficial. Eu faço um cheatsheet exaustivo de toda a estrutura de dados para sua referência.

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']
}
Cálculo
fonte
3

O pacote ParallelRegression fornece uma classe de conjunto ordenada setList () com mais método completo do que as opções baseadas na receita ActiveState. Ele suporta todos os métodos disponíveis para listas e a maioria, se não todos, métodos disponíveis para conjuntos.

RichardB
fonte
2

Como outras respostas mencionam, como no python 3.7+, o dict é ordenado por definição. Em vez de subclassificar OrderedDict, podemos subclassificar abc.collections.MutableSetou typing.MutableSetusar as chaves do dict para armazenar nossos valores.

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__()

Então apenas:

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

Coloquei esse código em uma pequena biblioteca , para que qualquer um possa fazer pip installisso.

bustawin
fonte
-4

Para muitos propósitos, basta chamar ordenado será suficiente. Por exemplo

>>> 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 você usar isso repetidamente, haverá uma sobrecarga ao chamar a função classificada, para que você queira salvar a lista resultante, desde que termine de alterar o conjunto. Se você precisar manter elementos exclusivos e classificados, concordo com a sugestão de usar OrderedDict de coleções com um valor arbitrário como Nenhum.

hwrd
fonte
43
O objetivo do OrderedSet é conseguir obter os itens na ordem em que foram adicionados ao conjunto. Você exemplo poderia talvez chamado SortedSet ...
Manutenção periódica
-4

Então, eu também tinha uma pequena lista em que claramente tinha a possibilidade de introduzir valores não exclusivos.

Eu procurei a existência de uma lista exclusiva de algum tipo, mas depois percebi que testar a existência do elemento antes de adicioná-lo funciona muito bem.

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

Não sei se existem advertências para essa abordagem simples, mas isso resolve meu problema.

Loïc N.
fonte
O principal problema dessa abordagem é que a adição de execuções em O (n). Isso significa que fica mais lento com grandes listas. Os conjuntos internos do Python são muito bons para tornar a adição de elementos mais rápida. Mas para casos de uso simples, certamente funciona!
Draconis