如何在Python中比较两个字典列表?

13

我应该如何比较两个dict列表?结果应该是从字典B的列表中找出不同的元素。

示例:

ldA = [{'user':"nameA", 'a':7.6, 'b':100.0, 'c':45.5, 'd':48.9},
       {'user':"nameB", 'a':46.7, 'b':67.3, 'c':0.0, 'd':5.5}]


ldB =[{'user':"nameA", 'a':7.6, 'b':99.9, 'c':45.5, 'd':43.7},
      {'user':"nameB", 'a':67.7, 'b':67.3, 'c':1.1, 'd':5.5},
      {'user':"nameC", 'a':89.9, 'b':77.3, 'c':2.2, 'd':6.5}]

在这里我想比较ldA和ldB。它应该打印出以下输出。

ldB -> {user:"nameA",  b:99.9, d:43.7}
ldB -> {user:"nameB",  a:67.7, c:1.1 }
ldb -> {user:"nameC", a:89.9, b:77.3, c:2.2, d:6.5}

我已经查看了下面的链接,但它只返回名称,而我想要像上面那样的名称和值。 在Python中比较字典列表以匹配列表并检测值更改的方法

没有任意结构的层次差异,因此您需要编写一个更复杂的算法,基于您对数据的了解。 user 是一个特殊键吗?它用于建立列表项之间的对应关系(假设ldB是无序的,结果是否应该相同)? - André Caron
对于整个程序以及此处,更合理的结构可能是类似于ldA = {'userA': {'a': 1, 'b': 2, ...}, ...} - Karl Knechtel
@Karl:看看我的解决方案,因为它使用了转换到该表示形式的方法。 - André Caron
6个回答

7

对于一个通用的解决方案,请考虑以下内容。即使用户在列表中顺序不同,它也会正确地进行差异比较。

def dict_diff ( merge, lhs, rhs ):
    """Generic dictionary difference."""
    diff = {}
    for key in lhs.keys():
          # auto-merge for missing key on right-hand-side.
        if (key not in rhs):
            diff[key] = lhs[key]
          # on collision, invoke custom merge function.
        elif (lhs[key] != rhs[key]):
            diff[key] = merge(lhs[key], rhs[key])
    for key in rhs.keys():
          # auto-merge for missing key on left-hand-side.
        if (key not not lhs):
            diff[key] = rhs[key]
    return diff

def user_diff ( lhs, rhs ):
    """Merge dictionaries using value from right-hand-side on conflict."""
    merge = lambda l,r: r
    return dict_diff(merge, lhs, rhs)

import copy

def push ( x, k, v ):
    """Returns copy of dict `x` with key `k` set to `v`."""
    x = copy.copy(x); x[k] = v; return x

def pop ( x, k ):
    """Returns copy of dict `x` without key `k`."""
    x = copy.copy(x); del x[k]; return x

def special_diff ( lhs, rhs, k ):
      # transform list of dicts into 2 levels of dicts, 1st level index by k.
    lhs = dict([(D[k],pop(D,k)) for D in lhs])
    rhs = dict([(D[k],pop(D,k)) for D in rhs])
      # diff at the 1st level.
    c = dict_diff(user_diff, lhs, rhs)
      # transform to back to initial format.
    return [push(D,k,K) for (K,D) in c.items()]

然后,您可以检查解决方案:
ldA = [{'user':"nameA", 'a':7.6, 'b':100.0, 'c':45.5, 'd':48.9},
       {'user':"nameB", 'a':46.7, 'b':67.3, 'c':0.0, 'd':5.5}]
ldB =[{'user':"nameA", 'a':7.6, 'b':99.9, 'c':45.5, 'd':43.7},
      {'user':"nameB", 'a':67.7, 'b':67.3, 'c':1.1, 'd':5.5},
      {'user':"nameC", 'a':89.9, 'b':77.3, 'c':2.2, 'd':6.5}]
import pprint
if __name__ == '__main__':
    pprint.pprint(special_diff(ldA, ldB, 'user'))

正如Karl在他的回答中指出的那样,由于你正在比较浮点数值,所以你需要在dict_diff函数中替换!=比较运算符为自定义比较运算符。或者,在这种情况下,你可以将lambda l,r: r替换为minmax(或任何适合你需求的函数)。 - André Caron
现在这才是工业级的!我认为dict_diff中的merge调用应该是user_diff - Karl Knechtel
@Karl:它的表现如广告所述,我实际上测试了所有内容。合并函数是user_diff,因为它是在special_diff中传递给dict_diff的。这种间接性允许使用相同的算法来区分列表和单个用户。 - André Caron
哦,抱歉,我错过了merge是一个参数。依赖注入,好东西 :) - Karl Knechtel
@qohelet,按照您的建议,我已经移除了.has_key() - André Caron

3

我的方法是:基于要排除的值的ldA构建一个查找表,然后确定从ldB中每个列表中排除适当的值的结果。

lookup = dict((x['user'], dict(x)) for x in ldA)
# 'dict(x)' is used here to make a copy
for v in lookup.values(): del v['user']

result = [
    dict(
        (k, v)
        for (k, v) in item.items()
        if item['user'] not in lookup or lookup[item['user']].get(k, v) == v
    )
    for item in ldB
]

然而,您需要知道的是,像这样比较浮点值是不可靠的。


1

我假设相应的 dict 在两个列表中是按照相同的顺序排列的。

在这种情况下,您可以使用以下代码:

def diffs(L1, L2):
    answer = []
    for i, d1 in enumerate(L1):
        d = {}
        d2 = L2[i]
        for key in d1:
            if key not in d1:
                print key, "is in d1 but not in d2"
            elif d1[key] != d2[key]:
                d[key] = d2[key]
        answer.append(d)
    return answer

未经测试。如果有错误,请评论,我会进行修正


首先,我想感谢您的回复。这里只返回了不同的值,但我需要来自ldB的特定用户不同的值。 - newbe
你所说的“用户特定”是什么意思?你是想比较字典中“User”值相同的部分,还是只想比较输入函数中指定的某些键? - inspectorG4dget

1

还有一种有点奇怪的解决方案(如果我漏掉了什么,对不起),但它也允许您配置自己的相等检查(您只需要修改isEqual lambda即可),并为您提供两个不同的选项来处理键不同时的情况:

ldA = [{'user':"nameA", 'a':7.6, 'b':100.0, 'c':45.5, 'd':48.9},
       {'user':"nameB", 'a':46.7, 'b':67.3, 'c':0.0, 'd':5.5}]


ldB =[{'user':"nameA", 'a':7.6, 'b':99.9, 'c':45.5, 'd':43.7},
      {'user':"nameB", 'a':67.7, 'b':67.3, 'c':1.1, 'd':5.5},
      {'user':"nameC", 'a':89.9, 'b':77.3, 'c':2.2, 'd':6.5}]

ldA.extend((ldB.pop() for i in xrange(len(ldB)))) # get the only one list here

output = []

isEqual = lambda x,y: x != y # add your custom equality check here, for example rounding values before comparison and so on

while len(ldA) > 0: # iterate through list
    row = ldA.pop(0) # get the first element in list and remove it from list
    for i, srow in enumerate(ldA):
        if row['user'] != srow['user']:
            continue
        res = {'user': srow['user']} #
        # next line will ignore all keys of srow which are not in row 
        res.update(dict((key,val) for key,val in ldA.pop(i).iteritems() if key in row and isEqual(val, row[key])))
        # next line will include the srow.key and srow.value into the results even in a case when there is no such pair in a row
        #res.update(dict(filter(lambda d: isEqual(d[1], row[d[0]]) if d[0] in row else True ,ldA.pop(i).items())))
        output.append(res)
        break
    else:
        output.append(row)

print output

0

我一段时间前写了这个工具,它目前可以处理嵌套的列表、字典和集合。它会给你一个更简洁的输出(. > i:1 > 'c' 中的 . 指的是顶层,i:1 指的是正在比较的列表中索引为 1 的元素):

compare(ldA, ldB)
. > i:0 > 'b' dict value is different:
100.0
99.9

. > i:0 > 'd' dict value is different:
48.9
43.7

. > i:1 > 'a' dict value is different:
46.7
67.7

. > i:1 > 'c' dict value is different:
0.0
1.1

. lists differed at positions: 2
['<not present>']
[{'c': 2.2, 'd': 6.5, 'a': 89.9, 'user': 'nameC', 'b': 77.3}]

0

这肯定对你的样本数据做了一些假设,主要是如果在ldA中没有在ldB中的用户,则此假设无效,请告诉我。

您可以像这样调用它:dict_diff(ldA, ldB, user)

def dict_diff(ldA, ldB, key):
    for i, dA in enumerate(ldA):
        d = {key: dA[key]}
        d.update(dict((k, v) for k, v in ldB[i].items() if v != dA[k]))
        print "ldB -> " + str(d)
    for dB in ldB[i+1:]:
        print "ldB -> " + str(dB)

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