问题 如何在Python中比较两个dicts列表?


我如何比较两个列表 dict?结果应该是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}

我已经通过以下链接,但它只返回名称,但我想要如上所述的名称和价值。

列表中的Dicts比较以匹配列表并检测Python中的值更改


7713
2018-06-13 16:48


起源

没有任意结构的层次差异,因此您需要根据您对数据的了解编写一个更复杂的算法。是 user 特别的钥匙?是否用于建立列表中项目之间的对应关系(假设 ldB 如果结果是相同的,是不正常的)? - André Caron
是的,用户特殊密钥在这里 - newbe
对于程序的其余部分以及此处,结构更像是更有意义 ldA = {'userA': {'a': 1, 'b': 2, ...}, ...}。 - Karl Knechtel
@Karl:查看我的解决方案,因为它使用了对该表示的转换。 - André Caron


答案:


对于一般解决方案,请考虑以下内容。即使用户在列表中出现故障,它也会正确区分。

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 (not rhs.has_key(key)):
            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 (not lhs.has_key(key)):
            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'))

7
2018-06-13 17:33



正如卡尔在答案中指出的那样,你需要更换 != 比较中 dict_diff 由于您要比较浮点值,因此自定义比较运算符起作用。或者,在这种情况下,您只需更换 lambda l,r: r 通过 min 要么 max(或任何适合您的需求)。 - André Caron
现在这是工业实力!我想打电话给 merge 在 dict_diff 应该是 user_diff 虽然。 - Karl Knechtel
@Karl:它像宣传的那样工作,我实际上测试了所有这些。合并功能是 user_diff,因为这是传递给谁的 dict_diff 在 special_diff。这种间接允许使用相同的算法来区分列表并区分各个用户。 - André Caron
对不起,我错过了 merge 是一个参数。依赖注入,好东西:) - Karl Knechtel
这很棒,很好的方法+1 - newbe


我的方法:根据要排除的值的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
]

但是,您应该知道,不能依赖比较这样的浮点值


3
2018-06-13 17:25



感谢回复+1 - newbe


我将假设相应的 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

未经测试。请评论是否有错误,我会解决它们


2
2018-06-13 17:01



首先,我要感谢您的回复,这里只返回不同的值,但我需要用户特定的不同值来自ldB - newbe
你是什​​么意思“用户特定”?你的意思是你想比较“用户”的值相同的词典,或者你的意思是你只想比较某些键,这些键将作为函数的输入提供? - inspectorG4dget


还有一个解决方案有点奇怪(抱歉,如果我错过了一些东西)但它也允许你配置你自己的相等检查(你只需要为此修改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

1
2018-06-13 19:37



@andrew +1,非常感谢你的回复 - newbe


这绝对需要您的示例数据中的一些假设,主要是不会有用户 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)

0
2018-06-13 17:25



+1,非常感谢您的回复 - newbe


我写了 这个工具 不久前,它目前可以处理嵌套列表,词组和集合。给你一个更简洁的输出( . 在 . > 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
2018-05-16 22:10