将列表转换为集合会更改元素顺序


119

最近,我注意到当我将a转换listset元素的顺序发生变化,由字符排序。

考虑以下示例:

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

131
@KarlKnechtel-是的“顺序对于数学中的集合是无意义的……”,但我遇到了现实世界中的问题:)
d.putto 2012年

在CPython 3.6+上unique = list(dict.fromkeys([1, 2, 1]).keys())。之所以有效,是因为dicts现在保留了插入顺序。
鲍里斯

Answers:


106
  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()返回的设置差为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
@Sean不,他们没有。None是语言保证的单例。在CPython中,实际成本只是指针(尽管成本始终存在,但对于dict,您几乎可以考虑None和其他单例或共享引用“免费”),因此,一个机器字,在现代计算机上可能为8字节。但是,是的,它不像一套电视机那样节省空间。
juanpa.arrivillaga

2
在CPython 3.6+上,您可以这样做,dict.fromkeys([1, 2, 1]).keys()因为常规dicts也保留顺序。
鲍里斯

@Boris从Python 3.7开始,这只是语言规范的一部分。尽管CPython实现在版本3.6中已经保留了插入顺序,但是这被视为实现细节,其他Python实现可能不会遵循。
Sven Marnach

@Sven我说过CPython。我到处都张贴了这篇文章,我已经厌倦了编写“ CPython 3.6或从Python 3.7开始的任何其他实现”。甚至没有关系,每个人都在使用CPython
Boris,

52

在Python 3.6中,set()现在应该保持顺序,但是对于Python 2和Python 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经常会自己散列stackoverflow.com/questions/45581901/…–
Chris_Rands

3
尝试x=[1,2,-1,20,6,210]使其成为一组。您会发现它根本没有排序,已经在Python 3.6中进行了测试。
GabrielChu

3
我不明白为什么这个答案有这么多的投票,为什么不保持插入顺序,也没有返回一个集合。
伊戈尔·罗德里格斯

20

回答第一个问题时,集合是针对集合操作进行优化的数据结构。像数学集一样,它不强制或维持元素的任何特定顺序。集合的抽象概念不强制执行顺序,因此不需要强制执行。从列表创建集合时,Python可以根据其用于集合的内部实现的需要自由更改元素的顺序,从而能够高效地执行集合操作。


9

通过以下功能删除重复项并保留顺序

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

检查此链接


好人,比我的解决方案还好:)
Tiger-222,

8

在数学中,有集合有序集合(osets)。

  • set:唯一元素的无序容器(实现)
  • oset:唯一元素的有序容器(未实现)

在Python中,仅直接实现集合。我们可以使用常规的dict键(3.7+)模拟osets 。

给定

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)是另外两种引人入胜的数学数据结构:

  • list:允许重复的元素的有序容器(已实现)
  • 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

在Sven的答案的基础上,我发现使用了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()的操作并获取集合:

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

您需要list(z.keys())获取列表输出。
jxn

在Python 3中,是的。不是在Python 2中,尽管我应该已经指定了。
jimh

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在搜索特定键方面要快得多,这在set操作中非常有用(这就是为什么需要set而不是list的原因)。

如果您真的在乎索引,只需将其保留为列表即可。如果仍要对许多列表中的元素进行设置操作,最简单的方法是为每个列表创建一个字典,该列表中的集合具有相同的键以及包含原始列表中所有键索引的list值。

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}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.