Вопрос по list, set, python – Как получить список объектов с уникальным атрибутом

12
Background

у меня естьlist. This list имеет много объектов. Каждый объект имеетid, Сейчас объекты бывают разных типов.

<code>objects = [Aobject, Bobject, Cobject]
</code>

где

<code>>>> Aobject != Bobject
True
>>> Aobject.id ==  Bobject.id
True
</code>
Problem

я хочуlist уникальных объектов на основеobject.id.

Что-то вроде этого:

<code>set(objects, key=operator.attrgetter('id'))
</code>

(Это не работает. Но я хочу что-то вроде этого)

Вы не должны перезаписывать встроенныеlist; назовите свою переменнуюlst или жеlist_ или что-то более значимое вместо этого. ThiefMaster♦

Ваш Ответ

6   ответов
0

Если вы можете изменить класс объектов, вы можете добавить соответствующие методы, которые используются при сравнении наборов:

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

Выход:

set([(OriginalExampleObject [A] [1]), (OriginalExampleObject [C] [2])])
set([(OriginalExampleObject [A] [1]), (OriginalExampleObject [C] [2])])
Error: User Rate Limit ExceededobjectError: User Rate Limit Exceeded
Error: User Rate Limit Exceededreturn id(self)Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededself.id != id(self)?
Error: User Rate Limit Exceededself.id.__hash__()Error: User Rate Limit Exceededreturn id(self)Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
3

Как насчет использованияdict (так как его ключи уникальны)?

Предполагая, что у нас есть

class Object:
    def __init__(self, id):
        self.id = id


Aobject = Object(1)
Bobject = Object(1)
Cobject = Object(2)
objects = [Aobject, Bobject, Cobject]

затемlist сObjectс уникальнымid поле может быть сгенерировано с помощьюdict понимание вPython 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()

Наконец мы можем написать функцию (Python 3 версия)

def unique(elements, key):
    return list({key(element): element for element in elements}.values())

гдеelements может быть любымiterable а такжеkey это какой-тоcallable который возвращаетсяhashable объекты изelements (key Quals кoperator.attrgetter('id') в нашем конкретном случае).

Ответ Марцина работает нормально, но не выглядит для меня Pythonic, так как понимание списка меняетсяseen объект из внешней области видимости, также есть некоторая магия за использованиеset.add метод и сравнение его результат (которыйNone) сobj.

И последняя, но не менее важная часть:

Benchmark

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

на моей машине дает

list comprehension + set:  0.20700953400228173
dict comprehension:  0.1477799109998159
Error: User Rate Limit Exceeded
2

Учитывая ваш список объектов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])]
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
16
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.

(Выражение может только оценитьNone еслиobj is None; в таком случае,obj.id поднимет исключение. В случаеmylist содержитNone значения, измените тест наif obj and (obj.id not in seen))

Обратите внимание, что это даст вам первый объект в списке, который имеет данный идентификатор. Ответ @ Abhijit даст вам последний такой объект.

Обновить:

В качестве альтернативы, приказ может быть хорошим выбором:

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())
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededorError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
-1

Довольно простой способ сделать это

for obj in mylist:
    if obj.id not in s:
        s.add(obj.id)

И это должно добавить любой идентификатор не видел. Требуемое время является линейным по размеру списка источников.

Error: User Rate Limit Exceededmylist?
0

Вы можете использоватьunique_everseen рецепт доступен вitertools документы, Это также доступно в сторонних библиотеках, напримерtoolz.unique, Обратите внимание, что этот метод сохранитfirst экземпляр объекта для данного атрибута.

from toolz import unique
from operator import attrgetter

res = list(unique(objects, key=attrgetter('id')))

Если ленивый итератор достаточен, вы можете опуститьlist преобразование.

Похожие вопросы