[英]How to get list of objects with unique attribute
我有一个list
。 这个list
有很多对象。 每个 object 都有一个id
。 现在对象是不同类型的。
objects = [Aobject, Bobject, Cobject]
在哪里
>>> Aobject != Bobject
True
>>> Aobject.id == Bobject.id
True
我想要一个基于object.id
的唯一对象list
。
像这样:
set(objects, key=operator.attrgetter('id'))
(这不起作用。但我想要这样的东西)
seen = set()
# never use list as a variable name
[seen.add(obj.id) or obj for obj in mylist if obj.id not in seen]
这是可行的,因为set.add
返回None
,所以列表理解中的表达式总是产生obj
,但前提是obj.id
尚未添加到seen
。
(如果obj is None
None
在这种情况下, obj.id
会引发异常。如果mylist
包含None
值,请将测试更改为if obj and (obj.id not in seen)
)
请注意,这将为您提供列表中具有给定 ID 的第一个 object。 @Abhijit 的回答会给你最后一个这样的 object。
更新:
或者,一个 ordereddict 可能是一个不错的选择:
import collections
seen = collections.OrderedDict()
for obj in mylist:
# eliminate this check if you want the last item
if obj.id not in seen:
seen[obj.id] = obj
list(seen.values())
使用dict
怎么样(因为它的键是唯一的)?
假设我们有
class Object:
def __init__(self, id):
self.id = id
Aobject = Object(1)
Bobject = Object(1)
Cobject = Object(2)
objects = [Aobject, Bobject, Cobject]
然后可以使用 Python 中的dict
理解生成具有Object
s unique by id
字段的list
3
unique_objects = list({object_.id: object_ for object_ in objects}.values())
在Python 2.7
unique_objects = {object_.id: object_ for object_ in objects}.values()
在Python <2.7
unique_objects = dict([(object_.id, object_) for object_ in objects]).values()
最后,我们可以写一个 function ( Python3 ,它也保留了插入顺序)
import sys
from typing import Callable, Hashable, List, Optional, Sequence, TypeVar
if sys.version_info < (3, 6):
from collections import OrderedDict as _OrderedDict
else:
# starting from Python3.6 `dict`s are insertion ordered by default
_OrderedDict = dict
_T = TypeVar('_T')
def unique(values: Sequence[_T],
key: Optional[Callable[[_T], Hashable]] = None) -> List[_T]:
"""
Returns unique values by given key (using value itself by default)
preserving order (taking first-from-start occurrence).
Time complexity: O(len(values))
Memory complexity: O(len(values
>>> unique([-1, 1, 0, 1])
[-1, 1, 0]
>>> unique([-1, 1, 0, 1], key=abs)
[-1, 0]
"""
return list(
_OrderedDict.fromkeys(values)
if key is None
else _OrderedDict((key(value), value)
for value in reversed(values)).values()
)
其中values
可以是任何sequence
(如果你有一个iterable
的 - 你可以在传递之前从它构建一个list
)并且key
是一些callable
的,它从每个values
返回可hashable
对象( key
等于operator.attrgetter('id')
在我们的特殊情况下)。
Marcin 的回答很好,但对我来说看起来不像 Pythonic,因为列表理解从外部 scope seen
object 发生突变,使用set.add
方法并将其结果(为None
)与obj
进行比较也有一些神奇之处。
最后但同样重要的部分:
import timeit
setup = '''
import random
class Object:
def __init__(self, id):
self.id = id
objects = [Object(random.randint(-100, 100))
for i in range(1000)]
'''
solution = '''
seen = set()
result = [seen.add(object_.id) or object_
for object_ in objects
if object_.id not in seen]
'''
print('list comprehension + set: ',
min(timeit.Timer(solution, setup).repeat(7, 1000)))
solution = '''
result = list({object_.id: object_
for object_ in objects}.values())
'''
print('dict comprehension: ',
min(timeit.Timer(solution, setup).repeat(7, 1000)))
在我的带有 Python3.8 的Linux机器上给出
list comprehension + set: 0.01755444100126624
dict comprehension: 0.012887613993370906
鉴于您的somelist
列表,有些列表类似于
[(Object [A] [1]), (Object [B] [1]), (Object [C] [2]), (Object [D] [2]), (Object [E] [3])]
你可以这样做
>>> {e.id:e for e in somelist}.values()
[(Object [B] [1]), (Object [D] [2]), (Object [E] [3])]
您可以使用itertools
文档中提供的unique_everseen
配方。 这在第 3 方库中也可用,例如toolz.unique
。 请注意,此方法将为给定属性保留 object 的第一个实例。
from toolz import unique
from operator import attrgetter
res = list(unique(objects, key=attrgetter('id')))
如果惰性迭代器就足够了,您可以省略list
转换。
如果可以更改对象的 class,则可以添加用于集合比较的适当方法:
# Assumption: this is the 'original' object
class OriginalExampleObject(object):
def __init__(self, name, nid):
self.name = name
self.id = nid
def __repr__(self):
return "(OriginalExampleObject [%s] [%s])" % (self.name, self.id)
class SetExampleObj(OriginalExampleObject):
def __init__(self, name, nid):
super(SetExampleObj, self).__init__(name, nid)
def __eq__(self, other):
return self.id == other.id
def __hash__(self):
return self.id.__hash__()
AObject = SetExampleObj("A", 1)
BObject = SetExampleObj("B", 1)
CObject = SetExampleObj("C", 2)
s = set()
s.add(AObject)
s.add(CObject)
print(s)
s.add(BObject)
print(s)
Output:
set([(OriginalExampleObject [A] [1]), (OriginalExampleObject [C] [2])])
set([(OriginalExampleObject [A] [1]), (OriginalExampleObject [C] [2])])
objects = [Aobject, Bobject, Cobject]
unique_objects = {o['id']:o for o in objects}.values()
一个相当简单的方法是
for obj in mylist:
if obj.id not in s:
s.add(obj.id)
这应该添加任何没有看到的 id。 花费的时间与源列表的大小成线性关系。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.