【问题标题】:Python fold/reduce composition of multiple dictionariesPython折叠/减少多个字典的组合
【发布时间】:2018-12-07 16:44:40
【问题描述】:

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

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 周围建立数据。

【问题讨论】:

  • 对于这种复杂的问题,编写自己的解决方案是完全可以的。正如下面的答案所示,您可以将问题分解为更小的部分并为每个部分编写一个函数。一旦你有了这些工作函数,你就可以将它们重构为更“优雅”的结构,比如用列表推导替换循环。
  • 是的,我知道,谢谢。我主要想看看我可能错过了哪些想法。
  • 您可能对我们的姐妹网站Code Review 感兴趣。在您编写了一个可行的解决方案后,社区非常乐意指出您可以改进代码的方法。这种评论似乎更符合您在这里要问的内容。

标签: python dictionary reduce fold


【解决方案1】:
import copy
from functools import partial, reduce

def traverse(seed, update, sentinel):
    for key, value in update.items():
        if isinstance(value, dict):
            try:
                traverse(seed[key], update[key], sentinel)
            except KeyError:
                seed[key] = value
        else:
            if key not in seed or value != seed[key] \
                    and key not in sentinel:
                seed[key] = value
                sentinel.add(key)
    return seed


def reduce_maps(seed, *updates):
    seed = copy.deepcopy(seed)
    return reduce(
        partial(traverse, sentinel=set()), [seed, *updates]
    )

【讨论】:

    【解决方案2】:

    给你:

    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
    

    我已经修正了我认为是您数据中的拼写错误(请参阅代码中的 cmets)。

    请注意,merge 可能需要根据确切的合并规则和可能的数据进行调整。要了解我的意思,请使用 merge = merge_simplest 并了解它失败的原因。如果“与数据无关”的形状(理解为不考虑叶子值的字典树)真的是一样的,那就不会了。

    【讨论】:

    • 非常感谢,正在研究您的解决方案。如果可能的话,我非常喜欢来避免递归,但我会尝试自己解决这个问题。
    • @jhrr 为什么要避免递归?
    • 这只是风格问题。我很高兴使用递归,但有些开发人员不喜欢,所以我只是在这里感受一下“解决方案空间”,可以这么说。
    • 其实只要递归使用生成器我认为就可以了。
    猜你喜欢
    • 1970-01-01
    • 2020-06-11
    • 2013-12-27
    • 2012-02-21
    • 1970-01-01
    • 2022-01-08
    • 2021-03-03
    • 2011-12-05
    • 1970-01-01
    相关资源
    最近更新 更多