Python,从列表中删除重复项并转换为one2many

时间:2012-08-27 11:18:12

标签: python list-comprehension

作为一个蟒蛇新手我需要解决这个非常简单的事情。 说我上课了:

class Event():
  eid = 0
  events = []

  def __repr__(self):
    return "id:"+str(self.eid) + "=>" + str(self.events)

  def __str__(self):
    return self.__repr__()

让我们创建一些实例并将它们保存到列表中

eventset = list()
e1 = Event()
e1.eid = 0
e1.events = [('1','2','3','A')]

e3 = Event()
e3.eid = 1
e3.events = [('4','5','6','A')]

e2 = Event()
e2.eid = 0
e2.events = [('7','8','9','A')]

e4 = Event()
e4.eid = 1
e4.events = [('10','11','12','A')]

eventset.append(e1,e2,e3,e4)

打印事件集给出:

[id:0=>[('1', '2', '3', 'A')], id:0=>[('7', '8', '9', 'A')], id:1=>[('4', '5', '6',   'A')], id:1=>[('10', '11', '12', 'A')]]

我想创建一个新列表,如下所示:

[id:0=>[('1', '2', '3', 'A'),('7', '8', '9', 'A')], id:1=>[('4', '5', '6','A'),('10', '11', '12', 'A')]]

如何做到这种优雅的“Pythonic方式”?

编辑:

  1. 需要保留列表中的事件元素的顺序

  2. 不想创建新的事件实例副本

4 个答案:

答案 0 :(得分:2)

您真正需要的是一个字典,其中键是eid,而项目是您的所有事件。我使用了集合中的defaultdict来为字典提供一个默认项目 - 在本例中是一个列表。

from collections import defaultdict

d = defaultdict(list)

for i in [e1,e2,e3,e4]:
   d[i.eid].append(i.events[0])

答案 1 :(得分:2)

我建议你“升级”Event课程:

class Event(object):  # <-- one change
    eid = 0
    events = []

    def __init__(self, eid=0, events=None): # <-- second change
        self.eid = eid
        if events is not None: self.events = list(events)

    def __repr__(self):
        return "id:"+str(self.eid) + "=>" + str(self.events)

    def __str__(self):
        return self.__repr__()

下一步:

from operator import add, attrgetter
from itertools import starmap, groupby

merge_event = lambda e, events: Event(e, reduce(add, map(attrgetter("events"), events), []))
list(starmap(merge_event, groupby([e1,e2,e3,e4], attrgetter("eid"))))

这里发生了什么

groupby返回带有元组列表的迭代器:(keyvalues):

>>> list(groupby([e1,e2,e3,e4], attrgetter("eid")))
[(0, <itertools._grouper object at 0x105d96bd0>), (1, <itertools._grouper object at 0x105d96f10>)]

其中key是您的分组条件,values是匹配项的迭代器。在此代码key = eid属性(attrgetter("eid"))和values =具有相同eid值的所有项目。

starmap与泛型map的行为相同,但是:a)返回迭代器而不是list,b)使用分离的参数调用给定的回调函数(f(*(key,value)) = f(key, values))。我们创建了特殊函数merge_event以使用groupby输出。

merge_event将(keyvalues)元组作为参数并生成一个Event对象。 key(实际上是eid)一切都很清楚。要创建事件列表,我使用reduce函数和add运算符(来自operator模块的函数表示)。它以这种方式工作:

>>> reduce(add, [[1,2,3], ["A","B","C"]], [])
[1, 2, 3, 'A', 'B', 'C']

最后,map(attrgetter("events"), events)仅收集Event个对象列表events属性的值(即事件列表)。

答案 2 :(得分:0)

@Burkan Khalid的解决方案是最简单的。

要想成为幻想,您可以将输出字典d转换为另一个事件列表:

grouped_events = []
for (i, v) in d:
    e = Event()
    e.eid = i
    e.events = v
    grouped_events.append(e)

当然,如果您的Event班级有__init__eidevents作为参数,那么这可以简化...

grouped_events = [Event(i,v) for (i,v) in d.items()]

答案 3 :(得分:0)

所以我觉得我找到了相当不错且非常优雅的解决方案。请看看并简化/简化。

我创建了一个迭代器,只有在尚未返回此eid时才会返回带有eid的元素。

class first_unique_iter(object):
  def __init__(self, mylist):
    self.eventset = mylist
    self.i = iter(mylist)
    self.used = []

  def __iter__(self):
    return self

  def next(self):
    element = self.i.next()
    if element.eid not in self.used:
        self.used.append(element.eid)
        return element
    else:
        return self.next()

然后是逻辑:

def slice_by_id(event, eventset):
   return [e for e in eventset if e.eid == event.eid]

def reduce_2one(x,y):
   x.events.extend(y.events)
   return x

final = [reduce(reduce_2one, slice_by_id(event,eventset)) for event in  first_unique_iter(eventset)]

因此,对于发现了唯一eid的每个第一个事件,我们使用这个新的迭代器运行list comp。拥有每个列表,我们需要从具有相同eid的事件中追加事件列表。这是在由eid列表切片调用的reduce()函数中完成的。

print final
>>> [id:0=>[('1', '2', '3', 'A'), ('7', '8', '9', 'A')], id:1=>[('4', '5', '6', 'A'), ('10', '11', '12', 'A')]]

你认为可以进一步简化吗?