Сглаживание вложенных словарей, сжатие ключей

Предположим, у вас есть словарь вроде:

{'a': 1,
 'c': {'a': 2,
       'b': {'x': 5,
             'y' : 10}},
 'd': [1, 2, 3]}

Как бы вы собрали его во что-то вроде:

{'a': 1,
 'c_a': 2,
 'c_b_x': 5,
 'c_b_y': 10,
 'd': [1, 2, 3]}
147
задан codeforester 16 April 2019 в 05:35
поделиться

5 ответов

def flatten_nested_dict(_dict, _str=''):
    '''
    recursive function to flatten a nested dictionary json
    '''
    ret_dict = {}
    for k, v in _dict.items():
        if isinstance(v, dict):
            ret_dict.update(flatten_nested_dict(v, _str = '_'.join([_str, k]).strip('_')))
        elif isinstance(v, list):
            for index, item in enumerate(v):
                if isinstance(item, dict):
                    ret_dict.update(flatten_nested_dict(item,  _str= '_'.join([_str, k, str(index)]).strip('_')))
                else:
                    ret_dict['_'.join([_str, k, str(index)]).strip('_')] = item
        else:
            ret_dict['_'.join([_str, k]).strip('_')] = v
    return ret_dict
1
ответ дан 23 November 2019 в 22:33
поделиться

Просто используйте python-benedict, это - подкласс dict, который предлагает много функций, включенных flatten метод. Возможно установить его с помощью зернышка: pip install python-benedict

https://github.com/fabiocaccamo/python-benedict#flatten

from benedict import benedict 

d = benedict(data)
f = d.flatten(separator='_')
-1
ответ дан 23 November 2019 в 22:33
поделиться

Изменение этого Сглаживает вложенные словари, сжимая ключи с max_level и пользовательским редуктором.

  def flatten(d, max_level=None, reducer='tuple'):
      if reducer == 'tuple':
          reducer_seed = tuple()
          reducer_func = lambda x, y: (*x, y)
      else:
          raise ValueError(f'Unknown reducer: {reducer}')

      def impl(d, pref, level):
        return reduce(
            lambda new_d, kv:
                (max_level is None or level < max_level)
                and isinstance(kv[1], dict)
                and {**new_d, **impl(kv[1], reducer_func(pref, kv[0]), level + 1)}
                or {**new_d, reducer_func(pref, kv[0]): kv[1]},
                d.items(),
            {}
        )

      return impl(d, reducer_seed, 0)
0
ответ дан 23 November 2019 в 22:33
поделиться

Использование рекурсии, сохраняя это простым и человекочитаемым:

def flatten_dict(dictionary, accumulator=None, parent_key=None, separator="."):
    if accumulator is None:
        accumulator = {}

    for k, v in dictionary.items():
        k = f"{parent_key}{separator}{k}" if parent_key else k
        if isinstance(v, dict):
            flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
            continue

        accumulator[k] = v

    return accumulator

Вызов прост:

new_dict = flatten_dict(dictionary)

или

new_dict = flatten_dict(dictionary, separator="_")

, если мы хотим изменить разделитель по умолчанию.

Немного разбивки:

, Когда функция сначала вызвана, это называют, только передавая dictionary, мы хотим сгладиться. accumulator параметр здесь для поддержки рекурсии, которую мы видим позже. Так, мы инстанцируем accumulator к пустому словарю, откуда мы поместим все вложенные значения оригинала dictionary.

if accumulator is None:
    accumulator = {}

, Поскольку мы выполняем итерации по значениям словаря, мы создаем ключ для каждого значения. parent_key аргумент будет None для первого вызова, в то время как для каждого вложенного словаря, он будет содержать ключ, указывающий на него, таким образом, мы будем предварительно ожидать тот ключ.

k = f"{parent_key}{separator}{k}" if parent_key else k

В случае, если значение v ключ k указывает, словарь, вызовы функции сам, передавая вложенный словарь, accumulator (который передается ссылкой, таким образом, все изменения, сделанные к нему, сделаны на том же экземпляре), и ключ k так, чтобы мы могли создать связанный ключ. Заметьте continue оператор. Мы хотим пропустить следующую строку, за пределами if блок, так, чтобы вложенный словарь не заканчивался в accumulator под ключом k.

if isinstance(v, dict):
    flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
    continue

Так, что мы делаем в случае, если значение v не является словарем? Просто поместите его неизменный в accumulator.

accumulator[k] = v

, После того как мы сделаны, мы просто возвращаемся эти accumulator, оставляя исходный dictionary аргумент нетронутым.

ПРИМЕЧАНИЕ

Это будет работать только со словарями, которые имеют строки как ключи. Это будет работать с hashable объектами, реализовывая __repr__ метод, но приведет к нежелательным результатам.

1
ответ дан 23 November 2019 в 22:33
поделиться

Я думал о подклассе UserDict к автоволшебно плоскому ключи.

class FlatDict(UserDict):
    def __init__(self, *args, separator='.', **kwargs):
        self.separator = separator
        super().__init__(*args, **kwargs)

    def __setitem__(self, key, value):
        if isinstance(value, dict):
            for k1, v1 in FlatDict(value, separator=self.separator).items():
                super().__setitem__(f"{key}{self.separator}{k1}", v1)
        else:
            super().__setitem__(key, value)

‌ преимущества это, что ключи могут быть добавлены на лету, или стандарт использования dict instanciation, без удивления:

‌

>>> fd = FlatDict(
...    {
...        'person': {
...            'sexe': 'male', 
...            'name': {
...                'first': 'jacques',
...                'last': 'dupond'
...            }
...        }
...    }
... )
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond'}
>>> fd['person'] = {'name': {'nickname': 'Bob'}}
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond', 'person.name.nickname': 'Bob'}
>>> fd['person.name'] = {'civility': 'Dr'}
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond', 'person.name.nickname': 'Bob', 'person.name.civility': 'Dr'}
0
ответ дан 23 November 2019 в 22:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: