如何比较嵌套的dicts

时间:2018-02-06 22:06:13

标签: python dictionary search hash set

a_standard = {
    'section1': {
        'category1': 1,
        'category2': 2
    },
    'section2': {
        'category1': 1,
        'category2': 2
    }

}

a_new = {
    'section1': {
        'category1': 1,
        'category2': 2
    },
    'section2': {
        'category1': 1,
        'category2': 3
    }

}

我想找到a_standarda_new之间的差异,a_new[section2][category2]价值差异为23

我应该将每个转换成一个集合,然后做差异或循环并比较字典吗?

3 个答案:

答案 0 :(得分:2)

有一个名为deepdiff的库,它具有很多选项,但我发现它有点不直观。

这是我在单元测试期间经常用来计算差异的递归函数。这超出了问题所要解决的范围,因为我也关心列表被嵌套的情况。希望您会发现它有用。

功能定义

from copy import deepcopy


def deep_diff(x, y, parent_key=None, exclude_keys=[], epsilon_keys=[]):
    """
    Take the deep diff of JSON-like dictionaries

    No warranties when keys, or values are None

    """
    EPSILON = 0.5
    rho = 1 - EPSILON

    if x == y:
        return None

    if parent_key in epsilon_keys:
        xfl, yfl = float_or_None(x), float_or_None(y)
        if xfl and yfl and xfl * yfl >= 0 and rho * xfl <= yfl and rho * yfl <= xfl:
            return None

    if type(x) != type(y) or type(x) not in [list, dict]:
        return x, y

    if type(x) == dict:
        d = {}
        for k in x.keys() ^ y.keys():
            if k in exclude_keys:
                continue
            if k in x:
                d[k] = (deepcopy(x[k]), None)
            else:
                d[k] = (None, deepcopy(y[k]))

        for k in x.keys() & y.keys():
            if k in exclude_keys:
                continue

            next_d = deep_diff(x[k], y[k], parent_key=k, exclude_keys=exclude_keys, epsilon_keys=epsilon_keys)
            if next_d is None:
                continue

            d[k] = next_d

        return d if d else None

    # assume a list:
    d = [None] * max(len(x), len(y))
    flipped = False
    if len(x) > len(y):
        flipped = True
        x, y = y, x

    for i, x_val in enumerate(x):
        d[i] = deep_diff(y[i], x_val, parent_key=i, exclude_keys=exclude_keys, epsilon_keys=epsilon_keys) if flipped else deep_diff(x_val, y[i], parent_key=i, exclude_keys=exclude_keys, epsilon_keys=epsilon_keys)

    for i in range(len(x), len(y)):
        d[i] = (y[i], None) if flipped else (None, y[i])

    return None if all(map(lambda x: x is None, d)) else d

# We need this helper function as well:
def float_or_None(x):
    try:
        return float(x)
    except ValueError:
        return None

用法

>>> deep_diff(a_standard, a_new)

{'section2': {'category2': (2, 3)}}

我认为输出比其他答案更直观。

在单元测试中,我会这样:

import json

diff = deep_diff(expected_out, out, exclude_keys=["flickery1", "flickery2"])
assert diff is None, json.dumps(diff, indent=2)

答案 1 :(得分:1)

您可以使用递归:

a_standard = {
'section1': {
    'category1': 1,
    'category2': 2
},
'section2': {
    'category1': 1,
    'category2': 2
 }

}

a_new = {
'section1': {
    'category1': 1,
    'category2': 2
},
'section2': {
    'category1': 1,
    'category2': 3
 }

}
def differences(a, b, section=None):
    return [(c, d, g, section) if all(not isinstance(i, dict) for i in [d, g]) and d != g else None if all(not isinstance(i, dict) for i in [d, g]) and d == g else differences(d, g, c) for [c, d], [h, g] in zip(a.items(), b.items())]

n = filter(None, [i for b in differences(a_standard, a_new) for i in b])

输出:

[('category2', 2, 3, 'section2')]

产生与不等值相对应的密钥。

编辑:没有列表理解:

def differences(a, b, section = None):
  for [c, d], [h, g] in zip(a.items(), b.items()):
      if not isinstance(d, dict) and not isinstance(g, dict):
         if d != g:
            yield (c, d, g, section)
      else:
          for i in differences(d, g, c):
             for b in i:
               yield b
print(list(differences(a_standard, a_new)))

输出:

['category2', 2, 3, 'section2']

此解决方案利用生成器(因此yield语句),它可以动态存储生成的值,只记住它停止的位置。可以通过将返回的结果作为列表转换来获取值。 yield可以更容易地累积值差异,并且无需在函数或全局变量中保留其他参数。

答案 2 :(得分:1)

假设密钥相同,你可以这样做:

class Player(pg.sprite.Sprite):
def __init__(self, game, x, y):
    self.groups = game.all_sprites
    pg.sprite.Sprite.__init__(self, self.groups)
    self.game = game
    self.image = pg.Surface((tilesize, tilesize))
    self.image.fill(yellow)
    self.rect = self.image.get_rect()
    self.vx, self.vy = 0, 0
    self.x = x * tilesize
    self.y = y * tilesize

def get_keys(self):
    self.vx, self.vy = 0, 0
    keys = pg.key.get_pressed()
    if keys[pg.K_LEFT] or keys[pg.K_a]:
        self.vx = -playerspeed
    if keys[pg.K_RIGHT] or keys[pg.K_d]:
        self.vx = playerspeed
    if keys[pg.K_UP] or keys[pg.K_w]:
        self.vy = -playerspeed
    if keys[pg.K_DOWN] or keys[pg.K_s]:
        self.vy = playerspeed
    if self.vx != 0 and self.vy != 0:
        self.vx *= 0.7071
        self.vy *= 0.7071

def collide_with_walls(self, dir):
    if dir == 'x':
        hits = pg.sprite.spritecollide(self, self.game.walls, False)
        if hits:
            if self.vx > 0:
                self.x = hits[0].rect.left - self.rect.width
            if self.vx < 0:
                self.x = hits[0].rect.right
            self.vx = 0
            self.rect.x = self.x
    if dir == 'y':
        hits = pg.sprite.spritecollide(self, self.game.walls, False)
        if hits:
            if self.vy > 0:
                self.y = hits[0].rect.top - self.rect.height
            if self.vy < 0:
                self.y = hits[0].rect.bottom
            self.vy = 0
            self.rect.y = self.y

def update(self):
    self.get_keys()
    self.x += self.vx * self.game.dt
    self.y += self.vy * self.game.dt
    self.rect.x = self.x
    self.collide_with_walls('x')
    self.rect.y = self.y
    self.collide_with_walls('y')

我现在正在手机上打字,很抱歉,如果语法有些混乱。