Python折叠/减少多个字典的组合

时间:2018-06-28 14:37:04

标签: python dictionary reduce fold

我想实现以下目标。它实际上是由任意数量的字典的组合或合并而成的,并参考“种子”或根字典,从而在最终结果中累积所有未更改和更新的值。

seed = {
    'update': False,
    'data': {
        'subdata': {
            'field1': 5,
            'field2': '2018-01-30 00:00:00'
        },
        'field3': 2,
        'field4': None
    },
    'data_updates': {},
    'subdata_updates': {},
    'diffs': {}
}

update_1 = {
    'update': True,
    'data': {
        'subdata': {
            'field1': 6,
            'field2': '2018-01-30 00:00:00'
        },
        'field3': 2,
        'field4': None
    },
    'data_updates': {},
    'subdata_updates': {'field1': 6},
    'diffs': {
        'field1': {
            'field': 'field1',
            'before': 5,
            'after': 6
        }
    }
}

update_2 = {
    'update': True,
    'data': {
        'subdata': {
            'field1': 5,
            'field2': '2018-01-30 00:00:00',
        },
        'field3': 2,
        'field4': 1
    },
    'data_updates': {'field4': 1},
    'subdata_updates': {},
    'diffs': {
        'field4': {
            'field': 'field4',
            'before': None,
            'after': 1
        }
    }
}

# I want to be able to pass in an arbitrary number of updates.
assert reduce_maps(*[seed, update_1, update_2]) == {
    'update': True,
    'data': {
        'subdata': {
            'field1': 6,
            'field2': '2018-01-30 00:00:00',
        },
        'field3': 2,
        'field4': 1
    },
    'data_updates': {'field4': 1},
    'subdata_updates': {'field1': 6},
    'diffs': {
        'field1': {
            'field': 'field1',
            'before': 5,
            'after': 6
        },
        'field4': {
            'field': 'field4',
            'before': None,
            'after': 1
        }
    }
}

您可以假设数据将始终保持这种形状,还可以假设每个有效载荷仅更新一个字段,并且没有两次更新将更新同一字段。

在这里,我可以隐约感觉到fold的类似物潜伏在这里,在seed周围的通道中建立数据。

由于数据总是相同的形状,因此手动编写函数来遍历有效载荷并将其手动添加到累加器中并不难,但我想知道是否存在更好,更通用的方法来做到这一点我可能不知道。我曾希望ChainMap可以这样工作

reduced = dict(ChainMap(*[update_1, update_2, seed]))

但这只会返回第一个映射,而丢弃其他映射。

1 个答案:

答案 0 :(得分:1)

您在这里:

from pprint import pprint


def merge_working(pre, post):
    if not (isinstance(pre, dict) and isinstance(post, dict)):
        return post

    new = pre.copy()  # values for unique keys of pre will be preserved
    for key, post_value in post.items():
        new[key] = merge_working(new.get(key), post_value)

    return new


def merge_simplest(pre, post):
    if not isinstance(pre, dict):
        return post
    return {key: merge_simplest(pre[key], post[key])
            for key in pre}


merge = merge_working


def reduce_maps(*objects):
    new = objects[0]
    for post in objects[1:]:
        new = merge(new, post)
    return new


seed = {
    'update': False,
    'data': {
        'subdata': {
            'field1': 5,
            'field2': '2018-01-30 00:00:00'
        },
        'field3': 2,
        'field4': None
    },
    'data_updates': {},
    'subdata_updates': {},
    'diffs': {}
}

update_1 = {
    'update': True,
    'data': {
        'subdata': {
            'field1': 6,
            'field2': '2018-01-30 00:00:00'
        },
        'field3': 2,
        'field4': None
    },
    'data_updates': {},
    'subdata_updates': {'field1': 6},
    'diffs': {
        'field1': {
            'field': 'field 1',
            'before': 5,
            'after': 6
        }
    }
}

update_2 = {
    'update': True,
    'data': {
        'subdata': {
            'field1': 5,
            'field2': '2018-01-30 00:00:00',
        },
        'field3': 2,
        'field4': 1
    },
    'data_updates': {'field4': 1},
    'subdata_updates': {},  # was subdata_update
    'diffs': {
        'field4': {
            'field': 'field 4',
            'before': None,
            'after': 1
        }
    }
}

result = reduce_maps(*[seed, update_1, update_2])

golden = {
    'update': True,
    'data': {
        'subdata': {
            'field1': 5,  # was 6
            'field2': '2018-01-30 00:00:00',
        },
        'field3': 2,
        'field4': 1
    },
    'data_updates': {'field4': 1},
    'subdata_updates': {'field1': 6},  # was subdata_update
    'diffs': {
        'field1': {
            'field': 'field 1',
            'before': 5,
            'after': 6
        },
        'field4': {
            'field': 'field 4',
            'before': None,
            'after': 1
        }
    }
}

pprint(result)
pprint(golden)

assert result == golden

我已修复您认为数据中存在错别字的问题(请参见代码中的注释)。

请注意,merge可能需要根据确切的合并规则和可能的数据进行调整。要了解我的意思,请使用merge = merge_simplest并了解其失败原因。并不是“数据不可知的”形状(理解为不考虑叶子值的字典树)是否真的相同。