Есть ли у Python упорядоченный набор?


477

Python имеет упорядоченный словарь . Как насчет заказанного набора?


18
как насчет обратного, мешок вещей? (неупорядоченный и неуникальный)
Вим

19
@wim collections.Counter- сумка Питона.
землетрясение

1
Что если что-то будет добавлено дважды? Какой должна быть позиция?
Маккей

2
@McKay - если бы он следовал поведению коллекций. OrdDict все равно находился бы в положении начального добавления
wojtow

Ответы:


206

Есть заказанный набор (возможна новая ссылка рецепт ), на который ссылается Документация Python 2 . Это работает на Py2.6 или позже и 3.0 или позже без каких-либо изменений. Интерфейс почти такой же, как обычный набор, за исключением того, что инициализация должна быть сделана со списком.

OrderedSet([1, 2, 3])

Это MutableSet, поэтому подпись для .unionне совпадает с сигнатурой набора, но поскольку она включает в себя __or__нечто подобное, можно легко добавить:

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

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

6
Я выбрал свой собственный ответ, потому что ссылка из документации приближает его к официальному ответу
Casebash

49
Интерфейс не точно так же , как обычный объект набора, многие основные методы отсутствуют , такие как update, union, intersection.
xApple


7
Я почти уверен, что вам не разрешено использовать два метода unionв одном и том же классе. Последний «победит», а первый не сможет существовать во время выполнения. Это потому, что OrderedSet.union(без паренов) должен ссылаться на один объект.
Кевин

3
Существует также пакет "selectedset", основанный на том же рецепте, но реализованный на Cython - pypi.python.org/pypi/orderedset .
mbdevpl

149

Упорядоченный набор является функционально частным случаем упорядоченного словаря.

Ключи словаря являются уникальными. Таким образом, если игнорировать значения в упорядоченном словаре (например, назначая ихNone ), то он, по сути, имеет упорядоченный набор.

Начиная с Python 3.1 есть collections.OrderedDict. Ниже приведен пример реализации OrderedSet. (Обратите внимание, что только несколько методов должны быть определены или переопределены: collections.OrderedDictи collections.MutableSetвыполнять тяжелую работу.)

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: да, один может понадобиться определить класс , OrderedSetкакие подклассы OrderedDictи abc.Setзатем определить __len__, __iter__и __contains__.
Stephan202

1
@ Stephan202: К сожалению, коллекции ABC живут collections, но в остальном хорошее предложение
u0b34a0f6ae

4
Это правда, но в результате у вас остается много потерянного пространства, что приводит к неоптимальной производительности.
Даниэль Катс

3
Дополнение; collection.OrderedDict также доступен в Python 2.7.
Нурблдофф

2
В результате OrderedSet([1,2,3])возникает ошибка TypeError. Как конструктор вообще работает? Отсутствующий пример использования.
xApple

90

Ответ - нет, но вы можете использовать collections.OrderedDictиз стандартной библиотеки Python только ключи (и значения как None) для той же цели.

Обновление : По состоянию на Python 3.7 (и CPython 3.6), стандарт dictбудет гарантированно сохранить порядок и более производительные , чем OrderedDict. (Однако для обратной совместимости и особенно читабельности вы можете продолжить использование OrderedDict.)

Вот пример того, как использовать dictв качестве упорядоченного набора, чтобы отфильтровать повторяющиеся элементы при сохранении порядка, тем самым эмулируя упорядоченный набор. Используйте dictметод класса, fromkeys()чтобы создать dict, затем просто попросите keys()обратную.

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

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

4
Возможно стоит упомянуть, что это также работает (быстрее) с ванилью dict.fromkeys(). Но в этом случае порядок ключей сохраняется только в реализациях CPython 3.6+, поэтому OrderedDictэто более переносимое решение, когда порядок имеет значение.
Jez

1
не будет работать, если значения не строковые
Anwar Hossain

4
@AnwarHossain keys = (1,2,3,1,2,1) list(OrderedDict.fromkeys(keys).keys())-> [1, 2, 3], python-3.7. Оно работает.
raratiru

1
Можем ли мы сделать вывод, что Set в Python 3.7+ также сохраняет порядок?
user474491

2
@ user474491 В отличие dict, setв Python 3.7+ , к сожалению , не сохраняет порядок.
CZ

39

Я могу сделать вас лучше, чем OrderedSet: boltons имеет чистый Python, 2/3-совместимый IndexedSetтип , который не только упорядоченное множество, но также поддерживает индексирование (как со списками).

Просто pip install boltons(или скопируйте setutils.pyв свою кодовую базу), импортируйте IndexedSetи:

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

Все уникально и сохранено в порядке. Полное раскрытие: я написал IndexedSet, но это также означает, что вы можете меня беспокоить, если есть какие-либо проблемы . :)


39

Реализации на PyPI

В то время как другие отмечают, что в Python нет встроенной реализации набора сохранения порядка вставки (пока), я чувствую, что в этом вопросе отсутствует ответ, в котором указано, что можно найти в PyPI .

Есть пакеты:

Некоторые из этих реализаций основаны на рецепте, опубликованном Раймондом Хеттингером в ActiveState. который также упоминается в других ответах здесь.

Некоторые отличия

  • упорядоченный набор (версия 1.1)
    • преимущество: O (1) для поиска по индексу (например my_set[5])
  • oset (версия 0.1.3)
    • преимущество: O (1) для remove(item)
    • недостаток: по-видимому, O (n) для поиска по индексу

Обе реализации имеют O (1) для add(item)и __contains__(item)( item in my_set).


2
Новый претендент - collection_extended.setlist . Такие функции, как set.union, не работают на нем, хотя он наследуется collections.abc.Set.
Timdiels

3
OrderedSetтеперь поддерживаетremove
warvariuc

17

Если вы используете упорядоченный набор для поддержания отсортированного порядка, рассмотрите возможность использования реализации отсортированного набора из PyPI. Модуль sortedcontainers предоставляет SortedSet именно для этой цели. Некоторые преимущества: чистый Python, реализация fast-as-C, 100% охват модульных тестов, часы стресс-тестирования.

Установка из PyPI легко с pip:

pip install sortedcontainers

Обратите внимание, что если вы не можете pip install, просто извлеките файлы sortedlist.py и sortedset.py из репозитория с открытым исходным кодом .

После установки вы можете просто:

from sortedcontainers import SortedSet
help(SortedSet)

Модуль sortedcontainers также поддерживает сравнение производительности с несколькими альтернативными реализациями.

Для комментария, который спрашивал о типе данных пакета Python, есть альтернативный тип данных SortedList, который можно использовать для эффективной реализации пакета.


Обратите внимание, что SortedSetкласс там требует, чтобы члены были сопоставимы и хэшируемы.
gsnedders

4
@gsnedders Встроенные, setа frozensetтакже требуют, чтобы элементы были хэшируемыми. Сопоставимое ограничение является дополнением для SortedSet, но это также очевидное ограничение.
получил

2
Как следует из названия, это не поддерживает порядок. Это ничего, кроме сортировки (set ([sequence])), что делает лучше?
2

@ldmtwo Я не уверен, на что вы ссылаетесь, но для ясности, SortedSet как часть Sorted Containers поддерживает отсортированный порядок.
GrantJ

2
@GrantJ - это разница между тем, поддерживает ли он порядок вставки или порядок сортировки . Большинство других ответов касаются порядка вставки. Я думаю, что вы уже знаете об этом, основываясь на первом предложении, но, вероятно, это то, что говорит ldmtwo.
Джастин

9

В случае, если вы уже используете панды в своем коде, его Indexобъект ведет себя почти как упорядоченный набор, как показано в этой статье .

Примеры из статьи:

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

Можете ли вы включить пример в этот ответ? Ссылки, как правило, ломаются через некоторое время.
Alechan

1
для разницы между наборами, которые вам действительно нужно использовать indA.difference(indB), знак минус выполняет стандартное вычитание
gg349

7

Немного опоздал к игре, но я написал класс, setlistкак часть collections-extendedкоторого полностью реализует SequenceиSet

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

Документация: http://collections-extended.lenzm.net/en/latest/

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


7

Там нет OrderedSetв официальной библиотеке. Я делаю исчерпывающую таблицу всех структур данных для вашей справки.

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

Пакет ParallelRegression предоставляет класс упорядоченного набора setList (), который является более полным методом, чем параметры, основанные на рецепте ActiveState. Он поддерживает все методы, доступные для списков, и большинство, если не все методы, доступные для множеств.


2

Как отмечают другие ответы, как и для python 3.7+, dict упорядочен по определению. Вместо того, чтобы создавать подклассы, OrderedDictмы можем создавать подклассы abc.collections.MutableSetили typing.MutableSetиспользовать ключи dict для хранения наших значений.

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

Тогда просто:

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

Я поместил этот код в небольшую библиотеку , так что любой может pip installэто сделать.


-4

Для многих целей достаточно просто отсортированного вызова. Например

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

Если вы собираетесь использовать это несколько раз, при вызове отсортированной функции возникнут дополнительные издержки, так что вы можете захотеть сохранить результирующий список, если вы закончили изменять набор. Если вам нужно сохранить уникальные элементы и отсортированные, я согласен с предложением использовать OrderedDict из коллекций с произвольным значением, таким как None.


43
Цель OrderedSet - получить элементы в том порядке, в котором они были добавлены в набор. Ваш пример может называться SortedSet ...
Периодическое обслуживание

-4

Таким образом, у меня также был небольшой список, в котором у меня была возможность ввести неуникальные значения.

Я искал наличие какого-то уникального списка, но потом понял, что тестирование существования элемента перед его добавлением работает просто отлично.

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

Я не знаю, есть ли предостережения к этому простому подходу, но он решает мою проблему.


Основная проблема этого подхода заключается в том, что добавление выполняется в O (n). Это означает, что с большими списками это становится медленнее. Встроенные в Python наборы очень хороши для ускорения добавления элементов. Но для простых случаев использования это, безусловно, работает!
Драконис
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.