Python свернуть / уменьшить состав нескольких словарей - PullRequest
0 голосов
/ 28 июня 2018

Я хочу добиться следующего. По сути, это состав или слияние произвольного числа словарей со ссылкой на «начальный» или корневой словарь, накапливающий все неизмененные и обновленные значения в конечном результате.

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.

Ответы [ 2 ]

0 голосов
/ 28 сентября 2018
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]
    )
0 голосов
/ 28 июня 2018

Вот, пожалуйста:

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 и поймите, почему это не удается. Не было бы, если бы «независимая от данных» форма (понимаемая как дерево словарей без учета значений листьев) была бы на самом деле одинаковой.

...