Python有有序集吗?
Posted
技术标签:
【中文标题】Python有有序集吗?【英文标题】:Does Python have an ordered set? 【发布时间】:2010-12-11 20:34:15 【问题描述】:Python 有一个ordered dictionary。那么有序集合呢?
【问题讨论】:
相反,一袋东西呢? (无序且不唯一) @wimcollections.Counter
是 Python 的包。
如果添加了两次怎么办?职位应该是什么?
@McKay - 如果它遵循 collections.OrderDict 的行为,它仍将处于初始添加的位置
警告:这里的几个答案已经过时了。例如,dict
现在是插入顺序的(从 Python 3.7 开始保证)
【参考方案1】:
对此有一个ordered set(可能是new link)配方,参考自Python 2 Documentation。这可以在 Py2.6 或更高版本以及 3.0 或更高版本上运行,无需任何修改。接口几乎和普通集合一样,只是初始化应该用一个列表来完成。
OrderedSet([1, 2, 3])
这是一个 MutableSet,因此 .union
的签名与 set 的签名不匹配,但由于它包含 __or__
,因此可以轻松添加类似的内容:
@staticmethod
def union(*sets):
union = OrderedSet()
union.union(*sets)
return union
def union(self, *sets):
for set in sets:
self |= set
【讨论】:
我选择了我自己的答案,因为文档中的参考使这接近官方答案 接口与普通的set对象不完全相同,缺少update
、union
、intersection
等许多必要的方法。
仅供参考,我注意到recipe cited in this answer 中的slightly modified version 已将added to PyPi 设为“有序集”
我很确定你不允许在同一个类中有两个方法都称为union
。最后一个将“获胜”,而第一个将在运行时不存在。这是因为OrderedSet.union
(没有括号)必须引用一个 single 对象。
还有一个基于相同配方但在 Cython 中实现的“orderedset”包——pypi.python.org/pypi/orderedset。【参考方案2】:
答案是否定的,但您可以将 Python 标准库中的 collections.OrderedDict
与键(和值作为 None
)一起用于相同目的。
更新:从 Python 3.7(和 CPython 3.6)开始,标准 dict
是 guaranteed to preserve order,并且比 OrderedDict
性能更高。 (然而,为了向后兼容,尤其是可读性,您可能希望继续使用OrderedDict
。)
这是一个示例,说明如何使用dict
作为有序集来过滤掉重复项,同时保持顺序,从而模拟有序集。使用dict
类方法fromkeys()
创建一个dict,然后简单地要求keys()
返回。
>>> keywords = ['foo', 'bar', 'bar', 'foo', 'baz', 'foo']
>>> list(dict.fromkeys(keywords))
['foo', 'bar', 'baz']
【讨论】:
也许值得一提的是,这也适用于(更快)香草dict.fromkeys()
。但在这种情况下,键顺序仅保留在 CPython 3.6+ 实现中,因此OrderedDict
在顺序很重要时是一种更便携的解决方案。
@AnwarHossain keys = (1,2,3,1,2,1)
list(OrderedDict.fromkeys(keys).keys())
-> [1, 2, 3]
,python-3.7。它有效。
我们能推断出 Python 3.7+ 中的 Set 也保留顺序吗?
@user474491 与dict
不同,Python 3.7+ 中的set
很遗憾不会保留顺序。
@DavidEhrmann 继续阅读同一链接:“2017 年 12 月更新:Python 3.7 保证字典保留插入顺序”【参考方案3】:
更新:从 Python 3.7 开始,此答案已过时。请参阅上面的jrc's answer 以获得更好的解决方案。仅出于历史原因将在此处保留此答案。
有序集在功能上是有序字典的一个特例。
字典的键是唯一的。因此,如果忽略有序字典中的值(例如,通过分配它们None
),那么它本质上是一个有序集合。
从 Python 3.1 和 2.7 开始,有 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 = property(lambda self: self.__sub__)
difference_update = property(lambda self: self.__isub__)
intersection = property(lambda self: self.__and__)
intersection_update = property(lambda self: self.__iand__)
issubset = property(lambda self: self.__le__)
issuperset = property(lambda self: self.__ge__)
symmetric_difference = property(lambda self: self.__xor__)
symmetric_difference_update = property(lambda self: self.__ixor__)
union = property(lambda self: self.__or__)
【讨论】:
@Casebash: 是的,可能需要定义一个类OrderedSet
,它是OrderedDict
和abc.Set
的子类,然后定义__len__
、__iter__
和__contains__
。
确实如此,但您确实会浪费大量空间,从而导致性能欠佳。
补充; collections.OrderedDict 在 python 2.7 中也可用。
执行OrderedSet([1,2,3])
会引发 TypeError。构造函数是如何工作的?缺少使用示例。
这个答案需要重写为:(1)支持使用元组列表进行初始化,(2)通过组合而不是继承使用dict
(因为它现在是有序的),以及(3 ) 使用collections.abc.MutableSet
。【参考方案4】:
PyPI 上的实现
虽然其他人指出 Python 中没有内置的插入顺序保留集实现(目前),但我觉得这个问题缺少一个答案,说明在 @987654321 上可以找到什么@。
有包:
ordered-set(基于 Python) orderedset(基于 Cython) collections-extended boltons(在 iterutils.IndexedSet 下,基于 Python) oset(最后更新于 2012 年)其中一些实现基于recipe posted by Raymond Hettinger to ActiveState,此处的其他答案中也提到了这一点。
一些差异
有序集(1.1 版) 优势:O(1) 用于按索引查找(例如my_set[5]
)
oset(版本 0.1.3)
优势:O(1) for remove(item)
缺点:显然 O(n) 用于按索引查找
对于add(item)
和__contains__(item)
(item in my_set
),这两种实现都有 O(1)。
【讨论】:
一个新的竞争者是collections_extended.setlist。像set.union
这样的函数在它上面不起作用,即使它继承了collections.abc.Set
。
OrderedSet
现在支持remove
还有来自 sortedcontainers 2.3.0 的 SortedSet
和一堆其他排序的东西。【参考方案5】:
我可以比 OrderedSet 做得更好:boltons 有 a pure-Python, 2/3-compatible IndexedSet
type,它不仅是一个有序集,而且还支持索引(与列表一样)。
只需 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
,但这也意味着you can bug me if there are any issues。 :)
【讨论】:
提供负索引时,索引不起作用。例如,这个 s[-4:-1] 在一个非常非空的集合上返回 IndexedSet([])。 @darlove 不确定您使用的是什么版本,但支持负索引,并且您提供的案例在您打开的问题上无法重现:github.com/mahmoud/boltons/issues/274【参考方案6】:如果您使用有序集来维护排序顺序,请考虑使用 PyPI 中的有序集实现。 sortedcontainers 模块为此目的提供了一个 SortedSet。一些好处:纯 Python、快速的 C 实现、100% 的单元测试覆盖率、数小时的压力测试。
使用 pip 从 PyPI 安装很容易:
pip install sortedcontainers
请注意,如果您不能pip install
,只需从open-source repository 中拉下sortedlist.py 和sortedset.py 文件即可。
安装后,您可以简单地:
from sortedcontainers import SortedSet
help(SortedSet)
sortedcontainers 模块还维护一个 performance comparison 以及几个替代实现。
对于询问 Python 的 bag 数据类型的评论,还有一个 SortedList 数据类型可用于有效地实现 bag。
【讨论】:
请注意,SortedSet
类要求成员具有可比较性和可散列性。
@gsnedders 内置的 set
和 frozenset
也要求元素是可散列的。可比较的约束是对SortedSet
的加法,但它也是一个明显的约束。
顾名思义,这并不能维持秩序。只有 sorted(set([sequence])) 更好?
@ldmtwo 我不确定您指的是哪个,但为了清楚起见,SortedSet 作为Sorted Containers 的一部分确实保持排序。
@GrantJ - 它是维护 insertion 顺序还是 sort 顺序之间的区别。大多数其他答案都与插入顺序有关。我想你已经根据你的第一句话意识到了这一点,但这可能就是 ldmtwo 所说的。【参考方案7】:
如果您已经在代码中使用了 pandas,它的 Index
对象的行为就像一个有序集,如 this article 所示。
文章中的例子:
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
【讨论】:
你能在这个答案中包含一个例子吗?链接往往会在一段时间后断开。 对于集合之间的差异,其实需要使用indA.difference(indB)
,减号进行标准减法
重要的是要注意pd.Index
允许重复元素,这是实际 Python set
所不期望的。【参考方案8】:
正如其他答案所提到的,对于 python 3.7+,字典是按定义排序的。除了继承 OrderedDict
之外,我们还可以使用 dict 的键来继承 abc.collections.MutableSet
或 typing.MutableSet
来存储我们的值。
import itertools
import typing
T = typing.TypeVar("T")
class OrderedSet(typing.MutableSet[T]):
"""A set that preserves insertion order by internally using a dict."""
def __init__(self, iterable: typing.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, None)
def __contains__(self, x: object) -> bool:
return self._d.__contains__(x)
def __len__(self) -> int:
return self._d.__len__()
def __iter__(self) -> typing.Iterator[T]:
return self._d.__iter__()
def __str__(self):
return f"', '.join(str(i) for i in self)"
def __repr__(self):
return f"<OrderedSet self>"
那么就:
x = OrderedSet([1, 2, -1, "bar"])
x.add(0)
assert list(x) == [1, 2, -1, "bar", 0]
I added this code, with some tests, in a small library,所以任何人都可以pip install
它。
【讨论】:
不要按原样使用。discard
永远不应该提出 KeyError
。另请注意,这并不能提供合理的__repr__
@JasonForbes 你是对的——事实上,我们在链接的 repo 中处理了你的 cmets。所以我只是在这个答案中带来了这些修复。感谢您指出! :-)【参考方案9】:
游戏有点晚了,但我编写了一个类 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
【讨论】:
【参考方案10】:官方库中没有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']
【讨论】:
这个备忘单中的一些奇怪的东西:根据collections.abc,序列是集合,而不是兄弟。并且迭代器不支持索引,因此不应该与列表和元组在同一个组中。另外,所有的 text_sequences 也是 Sequence【参考方案11】:正如其他人所说,OrderedDict
在功能方面是有序集的超集,但如果您需要一个与 API 交互的集并且不需要它是可变的,OrderedDict.keys()
其实是一个实现abc.collections.Set
:
import random
from collections import OrderedDict, abc
a = list(range(0, 100))
random.shuffle(a)
# True
a == list(OrderedDict((i, 0) for i in a).keys())
# True
isinstance(OrderedDict().keys(), abc.Set)
需要注意的是不可变性并且必须像字典一样构建集合,但它很简单并且只使用内置函数。
【讨论】:
【参考方案12】:ParallelRegression 包提供了一个setList( ) 有序集类,它比基于 ActiveState 配方的选项更完整。它支持所有可用于列表的方法以及大多数可用于集合的方法。
【讨论】:
【参考方案13】:所以我也有一个小列表,我显然有可能引入非唯一值。
我搜索了某种唯一列表的存在,但后来意识到在添加元素之前测试元素的存在就可以了。
if(not new_element in my_list):
my_list.append(new_element)
我不知道这种简单方法是否有注意事项,但它解决了我的问题。
【讨论】:
这种方法的主要问题是在 O(n) 中添加运行。这意味着它会随着大列表而变慢。 Python 的内置集合非常擅长使添加元素更快。但对于简单的用例,它确实有效!【参考方案14】:对于许多目的,只需调用 sorted 就足够了。例如
>>> 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]
如果您要重复使用它,调用 sorted 函数会产生开销,因此您可能希望保存结果列表,只要您完成更改集合。如果您需要维护唯一元素并进行排序,我同意从具有任意值(例如 None)的集合中使用 OrderedDict 的建议。
【讨论】:
OrderedSet 的目的是能够按照添加到集合中的顺序获取项目。您的示例可能称为 SortedSet...以上是关于Python有有序集吗?的主要内容,如果未能解决你的问题,请参考以下文章