【问题标题】:Merge lists of tuples based on its values根据其值合并元组列表
【发布时间】:2013-08-29 01:57:15
【问题描述】:

我正在尝试找出一种在 python 中合并两个列表的方法,以便完成这样的事情:

list_a = [(item_1, attribute_x), (item_2, attribute_y), (item_3, attribute_z)]
list_b = [(item_1, attribute_n), (item_3, attribute_p) ]

结果:

list_result = [(item_1, attribute_x, attribute_n), (item_2, attribute_y, False), (item_3, attribute_z, attribute_p)]

有什么想法吗?

【问题讨论】:

  • 发布一些真实数据。 item_1 可以散列吗?

标签: python list merge tuples key-value


【解决方案1】:

这是一个解决问题的有趣方法,这是一个强大的函数,它返回一个生成器:

def combine_item_pairs(l1, l2):
    D = {k:[v, False] for k, v in l1}
    for key, value in l2:
        if key in D:
            D[key][1] = value
        else:
            D[key] = [False, value]
    return (tuple([key]+value) for key, value in D.iteritems())

使用它:

>>> list(combine_item_pairs(list_a, list_b))
[('item_2', 'attribute_y', False), ('item_3', 'attribute_z', 'attribute_p'), ('item_1', 'attribute_x', 'attribute_n')]

这是一个额外的奖励解决方案(相同的界面,但更有效的解决方案:

from itertools import groupby
from operator import itemgetter as I

def combine_item_pairs(l1, l2):
    return (tuple(list([k]+[I(1)(i) for i in g]+[False])[:3]) for k, g in groupby(sorted(l1+l2), key=I(0)))

结果:

>>> list(combine_item_pairs(list_a, list_b))
[('item_1', 'attribute_n', 'attribute_x'), ('item_2', 'attribute_y', False), ('item_3', 'attribute_p', 'attribute_z')]

注意:如果列表需要大量排序或缺少大量值,则此解决方案的效率会降低。 (此外,目前所有缺勤情况将仅在元组的最后一项中由 False 值反映,无法知道哪个列表缺少项目(这是效率的代价)这应该与大数据一起使用知道哪个列表缺少项目并不重要)


编辑:计时器:

a = [('item_1', 'attribute_x'), ('item_2', 'attribute_y'), ('item_3', 'attribute_z')]
b = [('item_1', 'attribute_n'), ('item_3', 'attribute_p')]

def inbar(l1, l2):
    D = {k:[v, False] for k, v in l1}
    for key, value in l2:
        if key in D:
            D[key][1] = value
        else:
            D[key] = [False, value]
    return (tuple([key]+value) for key, value in D.iteritems())

def solus(l1, l2):
    dict_a,dict_b = dict(l1), dict(l2)
    items = sorted({i for i,_ in l1+l2})
    return [(i, dict_a.get(i,False), dict_b.get(i,False)) for i in items]

import timeit # running each timer 3 times just to be sure.
print timeit.Timer('inbar(a, b)', 'from __main__ import a, b, inbar').repeat()
# [2.2363221572247483, 2.1427426716407836, 2.1545361420851963]
# [2.2058199808040575, 2.137495707329387, 2.178640404817184]
# [2.4588094406466743, 2.4221991975274215, 2.3586636366037856]
print timeit.Timer('solus(a, b)', 'from __main__ import a, b, solus').repeat()
# [5.841498824468664, 5.951693880486182, 5.866254325691159]
# [5.843569212526087, 5.919173415087307, 6.027018876010061]
# [6.41402184345621, 6.229860036924308, 6.562849100520403]

【讨论】:

  • 这是一个聪明的解决方案。但是,一种更简单的方法 - 直接将列表转换为字典并遍历唯一键/项目 - 在内存和 CPU 使用方面更有效:[(i, a.get(i,False), b.get(i,False)) for i in {item for item,_ in list_a+list_b}] 我可以发布我使用的分析代码,但它很容易核实。 (注意:您的第二个更“有效”的解决方案实际上效率较低,并且混淆了属性的顺序。)
  • 请看一下计时器。如您所见,您错了。
  • 我想到了更大的投入。如果不是将具有 3 个项目的列表合并一百万次,而是将列表与一百万个项目合并一次,则结果会相反。我在 100 到 10m 个项目的列表上进行了测试。它在小型、非平凡的列表大小上并没有太慢,但扩展性很差。
【解决方案2】:

转换为字典并使用唯一项目列表:

a,b = dict(list_a), dict(list_b)
items = sorted({i for i,_ in list_a+list_b})

您可以按如下方式构建元组:

[(i, a.get(i,False), b.get(i,False)) for i in items]

使用您的示例:

item_1,item_2,item_3,item_4 = 1,2,3,4
attribute_x,attribute_y,attribute_z,attribute_n,attribute_p = 'x','y','z','n','p'

list_a = [(item_1, attribute_x), (item_2, attribute_y), (item_3, attribute_z)]
list_b = [(item_1, attribute_n), (item_3, attribute_p), (item_4, attribute_n)]

dict_a,dict_b = dict(list_a), dict(list_b)
items = sorted({i for i,_ in list_a+list_b})
list_result = [(i, dict_a.get(i,False), dict_b.get(i,False)) for i in items]

print(list_result)

结果:

[(1, 'x', 'n'), (2, 'y', False), (3, 'z', 'p'), (4, False, 'n')]

【讨论】:

  • 这种方案对电脑内存非常浪费,效率也非常低。此外 - 它根本不会扩展,并且不健壮。我相信你可以弄清楚如何解决所有这些问题,如果你希望你的答案是全面的,你应该这样做。目前,它几乎类似于暴力解决方案。
  • @inbar-rose 即使使用列表连接(itertools.chain 可能更节省内存)和不必要的排序,它也非常高效且可扩展性很好。如果这些项目是可散列的,那么将其扩展到多个项目列表是健壮且微不足道的。也许有一个更有效和 Pythonic 的解决方案,但它的性能并不像你想象的那么糟糕。事实上,我有没有提到它在 CPU 使用率和内存方面明显优于您的“高效”解决方案,并且两者都可以更好地扩展? (那点赞怎么样?;)
  • 请看一下我的解决方案中的计时器:stackoverflow.com/a/18447026/1561176 如您所见,您错了。
  • 尝试对超过 3 个元素的输入列表进行计时。
【解决方案3】:

使用字典,它们是一种非常灵活和可延展的数据结构:

dic_a = {}
dic_a['item_1'] = []
dic_a['item_1'].append(attribute_x)

对于每个元素,您可以列出值,然后如果要插入的键已经存在,则只需附加一个新值:

if 'item_1' in dic_result:
    dic_result['item_1'].append(attribute_n)

【讨论】:

  • 虽然你说的是真的 - 这并不是真的试图回答这个问题。您的答案只是对字典如何工作的解释。虽然字典可能是这个问题的一个很好的解决方案,但您实际上需要提供一个答案,而不是简单地使用来自问题的信息来创建字典。 (虽然我很欣赏你的优点,但请再努力一点)
猜你喜欢
  • 2013-09-01
  • 2017-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-24
  • 1970-01-01
  • 2013-09-17
  • 1970-01-01
相关资源
最近更新 更多