Преобразование списка в набор изменяет порядок элементов


121

В последнее время я заметил , что когда я Конвертирование listв setпорядок элементов изменяются и сортируются по характеру.

Рассмотрим этот пример:

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

Мои вопросы -

  1. Почему это происходит?
  2. Как я могу выполнять операции с настройками (особенно с настройкой разницы), не теряя первоначального порядка?

8
Почему вы не хотите потерять первоначальный порядок, особенно если вы выполняете операции с наборами? «порядок» - бессмысленное понятие для множеств не только в Python, но и в математике.
Karl Knechtel

131
@KarlKnechtel - Да, «порядок - бессмысленное понятие для множеств ... в математике», но у меня есть реальные проблемы :)
d.putto

На CPython 3.6+ unique = list(dict.fromkeys([1, 2, 1]).keys()). Это работает, потому что dictтеперь порядок вставки сохраняется.
Борис

Ответы:


107
  1. A set- это неупорядоченная структура данных, поэтому она не сохраняет порядок вставки.

  2. Это зависит от ваших требований. Если у вас есть обычный список, и вы хотите удалить некоторый набор элементов, сохраняя порядок списка, вы можете сделать это с пониманием списка:

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

    Если вам нужна структура данных, которая поддерживает как быстрые тесты членства, так и сохранение порядка вставки , вы можете использовать ключи словаря Python, который, начиная с Python 3.7, гарантированно сохраняет порядок вставки:

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

    bздесь действительно не нужно заказывать - вы также можете использовать set. Обратите внимание, что a.keys() - b.keys()возвращает установленную разницу как a set, поэтому порядок вставки не сохраняется.

    В старых версиях Python collections.OrderedDictвместо этого можно использовать :

    >>> 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
Ни один объект не стоит 16 байт. Если только есть OrderedSet () по умолчанию. :(
Шон

2
@ Шон, нет. Noneсинглтон с гарантированным языком. В CPython фактическая стоимость - это просто указатель (хотя эта стоимость всегда есть, но для dict вы можете почти считать, что Noneи другие одиночные или общие ссылки "бесплатными"), поэтому машинное слово, вероятно, 8 байт на современных компьютерах . Но да, это не так эффективно, как набор.
juanpa.arrivillaga

2
В CPython 3.6+ вы можете просто сделать это, dict.fromkeys([1, 2, 1]).keys()потому что обычный dictпорядок сохранения тоже.
Борис

@Boris Это только часть спецификации языка, начиная с Python 3.7. Хотя реализация CPython уже сохраняет порядок вставки в версии 3.6, это считается деталью реализации, которой могут не следовать другие реализации Python.
Свен Марнах

@Sven Я сказал CPython. Я выкладываю это повсюду, мне просто надоело писать «CPython 3.6 или любую другую реализацию, начиная с Python 3.7». Даже неважно, все используют CPython
Борис

53

В Python 3.6 set()теперь должен сохраняться порядок, но есть другое решение для Python 2 и 3:

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

8
Два примечания относительно сохранения порядка: только начиная с Python 3.6, и даже там он считается деталью реализации, поэтому не полагайтесь на него. Кроме того, ваш код очень неэффективен, потому что каждый раз при x.indexвызове выполняется линейный поиск. Если вас устраивает квадратичная сложность, вообще нет причин использовать a set.
Thijs van Dien,

27
@ThijsvanDien Это неверно, set()не упорядочено в Python 3.6, даже как деталь реализации, вы думаете о dicts
Chris_Rands

8
@ThijsvanDien Нет, они не отсортированы, хотя иногда появляются так, потому что они intчасто
хешируют

3
Попробуйте x=[1,2,-1,20,6,210]сделать это набором. Вы увидите, что он вообще не заказан, протестирован в Python 3.6.
GabrielChu

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

20

Отвечая на ваш первый вопрос, набор - это структура данных, оптимизированная для операций с наборами. Как и математический набор, он не обеспечивает и не поддерживает какой-либо конкретный порядок элементов. Абстрактная концепция набора не обеспечивает порядок, поэтому реализация не требуется. Когда вы создаете набор из списка, Python имеет право изменять порядок элементов для нужд внутренней реализации, которую он использует для набора, который может эффективно выполнять операции над наборами.



9

В математике есть множества и упорядоченные множества (oset).

  • set : неупорядоченный контейнер уникальных элементов (Реализовано)
  • oset : упорядоченный контейнер уникальных элементов (NotImplemented)

В Python напрямую реализованы только наборы. Мы можем эмулировать oset с помощью обычных клавиш dict ( 3.7+ ).

Дано

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

Код

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

Демо

Реплики удаляются, порядок вставки сохраняется.

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

Операции, подобные множеству, над клавишами dict.

oset - b
# {1, 20, 210}

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

oset & b
# {2, 6}

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

подробности

Примечание: неупорядоченная структура не препятствует упорядоченным элементам. Напротив, поддержание порядка не гарантируется. Пример:

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

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

Можно с удовольствием обнаружить, что список и мультимножество (mset) - это еще две увлекательные математические структуры данных:

  • список : упорядоченный контейнер элементов, который разрешает репликацию (реализовано)
  • mset : неупорядоченный контейнер элементов, разрешающий репликацию (NotImplemented) *

Резюме

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

* Мультимножество может быть косвенно эмулировано с collections.Counter()помощью dict-подобного отображения кратностей (счетчиков).


4

Как указано в других ответах, наборы - это структуры данных (и математические концепции), которые не сохраняют порядок элементов -

Однако, используя комбинацию наборов и словарей, вы можете добиться того, чего захотите - попробуйте использовать эти фрагменты:

# 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

Основываясь на ответе Свена, я обнаружил, что с помощью collections.OrderedDict, как так, помог мне выполнить то, что вы хотите, плюс разрешил мне добавить больше элементов в 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)])

Если вы хотите добавить элементы, но при этом относитесь к ним как к набору, вы можете просто сделать:

z['nextitem']=None

И вы можете выполнить операцию вроде z.keys () над dict и получить набор:

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

вам нужно сделать, list(z.keys())чтобы получить вывод списка.
jxn

в Python 3 да. не в Python 2, хотя я должен был указать.
Джим

0

Реализация вышеизложенной концепции наивысшего балла, которая возвращает его в список:

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

Протестировано (кратко) на Python 3.6 и Python 2.7.


0

В случае, если у вас есть небольшое количество элементов в ваших двух исходных списках, для которых вы хотите выполнить операцию установки разницы, вместо использования, collections.OrderedDictкоторое усложняет реализацию и делает ее менее читаемой, вы можете использовать:

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

Его временная сложность не так хороша, но он аккуратный и легко читаемый.


0

Интересно, что люди всегда используют «проблему реального мира», чтобы пошутить над определением в теоретической науке.

Если в наборе есть порядок, сначала нужно разобраться со следующими проблемами. Если в вашем списке есть повторяющиеся элементы, в каком порядке вы должны превратить его в набор? В каком порядке мы объединяем два множества? Каков порядок, если мы пересекаем два множества с разным порядком на одних и тех же элементах?

Кроме того, set намного быстрее при поиске определенного ключа, что очень хорошо для работы с наборами (и поэтому вам нужен набор, а не список).

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

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}

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.