繁体   English   中英

如何获取具有唯一属性的对象列表

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM