基于其值合并元组列表

4
我正在尝试找出一种方法来合并两个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是可哈希的吗? - Ashwini Chaudhary
3个回答

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}] 我可以发布我使用的分析代码,但很容易验证。 (注意:您的第二个更“高效”的解决方案实际上不太高效,并混淆了属性的顺序。) - Richard
请看一下计时器。正如您所看到的,您是错误的。 - Inbar Rose
我考虑的是更大的输入。如果不是一遍又一遍地合并只有3个项目的列表,而是一次合并有100万个项目的列表,则结果将被颠倒。我测试了100到1000万个项目的列表。在小型、非平凡的列表大小上,速度并不太慢,但是扩展性很差。 - Richard

0

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

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)

虽然你说的是对的,但这并不是回答问题的尝试。你的回答只是解释了字典的工作原理。虽然字典可能是这个问题的一个很好的解决方案,但你需要提供一个实际上不仅仅是使用问题中的信息来创建字典的答案。(虽然我欣赏你的努力,请再努力一点) - Inbar Rose

-1

将数据转换为字典并使用唯一项列表:

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

1
这个解决方案对计算机的内存非常浪费,而且效率非常低下。此外,它根本无法扩展,并且不够健壮。我相信你可以想出如何解决所有这些问题,如果你想让你的答案全面一些,那么你应该这样做。目前,它几乎类似于一种蛮力解决方案。 - Inbar Rose
即使使用列表拼接(itertools.chain 可能更节省内存)和不必要的排序,它仍然出奇地高效并且可扩展到多个项目列表,只要这些项目是可哈希的,它就很健壮。 也许有更有效且更符合 Python 风格的解决方案,但它的表现并没有像你想象的那样糟糕。 事实上,我是否提到了它在 CPU 使用率和内存方面比您的“高效”解决方案表现显著,并且在两者方面都具有更好的可扩展性? (怎么样,点个赞吧?;) - Richard
请查看我解决方案中的计时器:https://dev59.com/KnbZa4cB1Zd3GeqPCB5y#18447026,如您所见,您是错误的。 - Inbar Rose
尝试对包含超过3个元素的输入列表进行计时。 - Richard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接