比较两个元组列表

5
old = [('ver','1121'),('sign','89'),('address','A45'),('type','00')]
new = [('ver','1121'),('sign','89'),('type','01')]

我需要根据元组的第一个元素比较新列表和旧列表,并显示新列表中有哪些元素不同,输出应如下所示:

我需要根据元组的第一个元素比较新列表和旧列表,并显示新列表中有哪些元素不同,输出应如下所示:

Match     : ver   =   1121
Match     : sign  =   89
Mismatch  : type  =   01 (old : 00)

我可以使用以下列表推导式获取所有匹配的元组,但是我无法想到更多的方法。
my_list = [(a,b) for (a,b) in new for (c,d) in old  if ((a==c) and (b==d))]
print( my_list)

请给我提供一种方法来实现它。 编辑 抱歉我的问题没有表述清楚,我漏掉了一件事情,列表中的键可以是重复的,这意味着列表可能像这样:
old = [('ver','1121'),('sign','89'),('address','A45'),('type','00'),('ver','sorry')]

new = [('ver','1121'),('sign','89'),('type','01'),('ver','sorry)]

更新

感谢@holdenweb的帮助,我对他的代码进行了一些更改,并且现在似乎可以得到预期的输出结果,请指出是否有任何缺陷。

old = [('ver','1121'),('sign','89'),('address','A45'),('type','00'),('ver','works?')]
new = [('ver','1121'),('sign','89'),('type','01'),('ver','This')]

formatter = "{:12}: {:8} = {}".format
newfmter = "{} (old : {})".format

kv_old = []
for i,(kn, vn) in enumerate(new):
    vo = [(j,(ko,vo)) for j,(ko, vo) in enumerate(old) if (ko==kn) ]
    for idx,(key,val) in vo:
        if idx >=i:
            kv_old = [key,val]
            break;

    if kv_old[1]==vn:
        print(formatter("Match", kv_old[0], kv_old[1]))
    else:
        print(formatter("Mismatch", kn, newfmter(vn, kv_old[1])))

3
address会发生什么变化? - MooingRawr
请提出其他方法,例如遍历列表,如果列表推导式无法实现。 - Ron
由于它不在这两个列表中,所以什么也没有 @MooingRawr - Mr. Xcoder
@MooingRawr,比较应该仅适用于新项目中可用的项。 - Ron
输出的顺序重要吗?例如列表的顺序:应该先是“ver”,其次是“sign”。 - MooingRawr
@MooingRawr 是的,顺序很重要,应该与新列表中的顺序相同。 - Ron
5个回答

13

set是一种用于查找这种不匹配的强大工具:

>>> old = [('ver','1121'),('sign','89'),('address','A45'),('type','00')]
>>> new = [('ver','1121'),('sign','89'),('type','01')]

>>> print('no longer there:', set(old) - set(new))
no longer there: {('type', '00'), ('address', 'A45')}

>>> print('newly added:', set(new) - set(old))
newly added: {('type', '01')}

>>> print('still there:', set(old) & set(new))
still there: {('sign', '89'), ('ver', '1121')}

1
这是一个工具,OP可以用它来找出不匹配的地方 :) - Brian Rodriguez

1
这是什么意思?
n,o=dict(new),dict(old)
for i in n:
    print "{0:10}:{2:8} {3:8} {1}".format(*(("Match","") if o.get(i)==n[i] else ("Mismatch",o.get(i,i)))+ (i,n[i]))

输出:

Mismatch  :type     01       00
Match     :ver      1121     
Match     :sign     89      

如果你需要顺序,尝试使用OrderedDict:
from collections import OrderedDict
n,o=OrderedDict(new),OrderedDict(old)

1
有时列表推导式不是答案。这可能是其中之一。另外,您没有处理一个键存在于“old”中但不存在于“new”中的情况 - 尽管您可以在此处包含该代码,但如果不相关,则可以削减该代码。您也可以类似地处理从“new”中缺少的键的情况,但我没有做到那么远。
old = [('ver','1121'),('sign','89'),('address','A45'),('type','00')]
new = [('ver','1121'),('sign','89'),('type','01'),("sneaky", 'not there before')]
formatter = "{:12}: {:8} = {}".format
newfmter = "{} (old : {})".format

for (kn, vn) in new:
    if any(ko==kn for (ko, vo) in old):
        ko, vo = [(ko, vo) for (ko, vo) in old if ko==kn][0]
        if vo==vn:
            print(formatter("Match", ko, vo))
        else:
            print(formatter("Mismatch", kn, newfmter(vn, vo)))
    else:
        print(formatter("New", kn, vn))

我们可以使用 enumerate() 来迭代遍历两个列表以处理重复的键吗? - Ron
“enumerate”只是为序列中的项目分配数字,所以我不确定它如何有帮助。但如果列表变得很大,有许多可能的策略。首先想到的是,为每个列表构建一个字典,其键是元组的第一个元素,值是该元组在列表中的位置。这将使确定正确的列表元素更快,而无需迭代它(这是O(n / 2)操作)。 - holdenweb

0

你可以这样做:

old = [('ver','1121'),('sign','89'),('address','A45'),('type','00')]
new = [('ver','1121'),('sign','89'),('type','01')]
my_list = [(a,b) for (a,b) in new for (c,d) in old  if ((a==c) and (b==d))]
for i in old:
     if i in my_list:
             print "Match : ", i
     else:
             print "Mismatch : ", i

这将为您提供:

Match :  ('ver', '1121')
Match :  ('sign', '89')
Mismatch :  ('address', 'A45')
Mismatch :  ('type', '00')

但是肯定有一种更“Pythonic”的方式...


0
这里有一个一行代码可以获取所有比较的列表。根据你的旧列表和新列表的大小,使用集合推导可能会更快一些,但是我的sorted+itertools.groupby方法会抵消这种速度(因为sorted返回一个list):
comps = [(key, 'changed', old_val, new_val) 
          if old_val != new_val else (key, 'same', old_val)
           for key, old_val in old for oth_key, new_val in new
             if key == oth_key]

comps现在是:

[('ver', 'same', '1121'),
 ('sign', 'same', '89'),
 ('type', 'changed', '00', '01')]

打印出来:

for t in comps:
    if len(t) == 3:
        print('%s: %s, value: %s' % (t[1], t[0], t[2]))
    else:
        print('%s: %s, value: %s' % (t[1], t[0], ', '.join(t[2:])))

same: ver, value: 1121
same: sign, value: 89
changed: type, value: 00, 01

编辑:以下内容并不完全符合OP的要求,但我仍然会保留它,以供那些想看看先前保持不变的元素和后来发生变化的元素的人参考(实际上,如果您想按自定义语法排序以查看首先更改的元素,您可以定义一个自定义语法,但这取决于您)。

为了准备使用itertools.groupby,并考虑到OP对上述打印顺序的要求,我们可以使用collections.OrderedDict对象来创建有效地“有序集”中的“键”:

import collections

proper_order = tuple(collections.OrderedDict.fromkeys([x[0] for x in new]).keys())

proper_order
Out[43]: ('ver', 'sign', 'type')

现在根据`proper_order`中的自定义语法对`comps`进行排序:
comps_sorted = sorted(comps, key=lambda x: proper_order.index(x[0]))

comps_sorted
Out[45]: 
[('ver', 'same', '1121'),
 ('sign', 'same', '89'),
 ('type', 'changed', '00', '01')]

使用 itertools.groupby 打印:

for key, group in itertools.groupby(comps_sorted, key=lambda x: x[1]):
    for g in group:
        print('%s: %s' % (key, ', '.join(x for x in g if x not in ('same', 'changed'))))

same: ver, 1121
same: sign, 89
changed: type, 00, 01

碰巧这个输出的顺序与 OP 上面请求的顺序相同,但对于更大的情况,两种方法之间的顺序差异将变得明显。


“Order matters” 是什么意思?你的意思是在“changed”之前需要“same”吗? - blacksite
@not_a_robot,如果旧列表和新列表中都有重复的键,它还能正常工作吗? 比如说,旧列表和新列表都有一个额外的元组 ('ver','blah')。 - Ron

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