Раскрытый вложенный словарь Python - PullRequest
0 голосов
/ 17 сентября 2018

Какой самый чистый способ конвертировать это

{"a.b.c[0].key1": 1, "a.b.c[1].key2": 2, "a.b.c[3].key3": 3}

В это

{"a": {"b": {"c": [{"key1": 1}, {"key2": 2}, None, {"key3": 3}]}}}
  • ключи словаря могут быть чем угодно.
  • длина списка может варьироваться.
  • глубина словаря может варьироваться.
  • если в списке отсутствуют значения, значение должно быть None.
  • если значения повторяются, то учитывается последнее объявленное значение.

Я придумал следующий рабочий пример.

Интересно, сможем ли мы найти лучшее решение для нашего сообщества.

def unflatten(data):
    if type(data) != dict:
        return None
    regex = r'\.?([^.\[\]]+)|\[(\d+)\]'
    result_holder = {}
    for key,value in data.items():
        cur = result_holder
        prop = ""
        results = re.findall(regex, key)
        for result in results:
            prop = int(prop) if type(cur) == list else prop
            if (type(cur) == dict and cur.get(prop)) or (type(cur) == list and len(cur) > prop):
                cur = cur[prop]
            else:
                if type(cur) == list:
                    if type(prop) is int:
                        while len(cur) <= prop:
                            cur.append(None)
                cur[prop] = list() if result[1] else dict()
                cur = cur[prop]
            prop = result[1] or result[0]

        prop = int(prop) if type(cur) == list else prop

        if type(cur) == list:
            if type(prop) is int:
                while len(cur) <= prop:
                    cur.append(None)

        print(len(cur), prop)
        cur[prop] = data[key]

    return result_holder[""] or result_holder

Ответы [ 3 ]

0 голосов
/ 18 сентября 2018

Вы можете использовать рекурсию:

d = {"a.b.c[0].key1": 1, "a.b.c[1].key2": 2, "a.b.c[3].key3": 3}
from itertools import groupby
import re
def group_data(data):
  new_results = [[a, [i[1:] for i in b]] for a, b in groupby(sorted(data, key=lambda x:x[0]), key=lambda x:x[0])]
  arrays = [[a, list(b)] for a, b in groupby(sorted(new_results, key=lambda x:x[0].endswith(']')), key=lambda x:x[0].endswith(']'))]
  final_result = {}
  for a, b in arrays:
     if a:
       _chars = [[c, list(d)] for c, d in groupby(sorted(b, key=lambda x:re.findall('^\w+', x[0])[0]), key=lambda x:re.findall('^\w+', x[0])[0])]
       _key = _chars[0][0]
       final_result[_key] = [[int(re.findall('\d+', c)[0]), d[0]] for c, d in _chars[0][-1]]
       _d = dict(final_result[_key])
       final_result[_key] = [group_data([_d[i]]) if i in _d else None for i in range(min(_d), max(_d)+1)]
     else:
        for c, d in b:
           final_result[c] = group_data(d) if all(len(i) >1 for i in d) else d[0][0]
  return final_result

print(group_data([[*a.split('.'), b] for a, b in d.items()]))

Выход:

{'a': {'b': {'c': [{'key1': 1}, {'key2': 2}, None, {'key3': 3}]}}}
0 голосов
/ 18 сентября 2018

Вот еще один вариант того, как достичь желаемых результатов. Не так красиво, как хотелось бы, поэтому я ожидаю, что есть гораздо более элегантный способ. Вероятно, больше регулярных выражений, чем на самом деле необходимо, если вы потратили немного больше времени на это, а также кажется, что break подход к обработке окончательного ключа, вероятно, является просто индикатором того, что логика цикла может быть улучшена для устранения такого рода ручного вмешательства , Тем не менее, надеюсь, это полезно в процессе уточнения вашего подхода здесь.

import re

def unflatten(data):
    results = {}
    list_rgx = re.compile(r'[^\[\]]+\[\d+\]')
    idx_rgx = re.compile(r'\d+(?=\])')
    key_rgx = re.compile(r'[^\[]+')
    for text, value in data.items():
        cur = results
        keys = text.split('.')
        idx = None
        for i, key in enumerate(keys):
            stop = (i == len(keys) - 1)          
            if idx is not None:
                val = value if stop else {}  
                if len(cur) > idx:
                    cur[idx] = {key: val}
                else:
                    for x in range(len(cur), idx + 1):
                        cur.append({key: val}) if x == idx else cur.append(None)         
                if stop:
                    break
                else:
                    cur[idx].get(key)
                    idx = None
            if stop:
                cur[key] = value
                break
            elif re.match(list_rgx, key):
                idx = int(re.search(idx_rgx, key).group())
                key = re.search(key_rgx, key).group()
                cur.setdefault(key, [])
            else:
                cur.setdefault(key, {})
            cur = cur.get(key)
    print(results)

Выход:

d = {"a.b.c[0].key1": 1, "a.b.c[1].key2": 2, "a.b.c[3].key3": 3}
unflatten(d)

# {'a': {'b': {'c': [{'key1': 1}, {'key2': 2}, None, {'key3': 3}]}}}
0 голосов
/ 17 сентября 2018

Рекурсивная функция, вероятно, будет намного проще работать и более элегантна.

Это частично псевдокод, но он может помочь вам задуматься.

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

def unflatten(data):
    resultDict = {}
    for e in data:
        insertElement(e.split("."), data[e], resultDict)

    return resultDict


def insertElement(path, value, subDict):
    if (path[0] is of the form "foo[n]"):
        key, index = parseListNotation(path[0])
        if (key not in subDict):
            subDict[key] = []

        if (index >= subDict[key].len()):
            subDict[key].expandUntilThisSize(index)

        if (subDict[key][index] == None):
            subDict[key][index] = {}

        subDict[key][index] = insertElement(path.pop(0), value, subDict[key][index])

    else:
        key = path[0]

        if (path.length == 1):
            subDict[key] = value
        else:
            if (key not in subDict):
                subDict[key] = {}

            subDict[key] = insertElement(path.pop(0), value, subDict[key])

    return subDict;

Идея состоит в том, чтобы построить словарь изнутри. E.g.:

Для первого элемента сначала создайте словарь `

  • {key1: 1},

Затем присвойте это элементу нового словаря

  • {c : [None]}, c[0] = {key1: 1}

Затем присвойте этот словарь следующему элементу b в новом dict, как - {b: {c : [{key1: 1}]}

Присвойте этот результат a в новом дикте. - {a: {b: {c : [{key1: 1}]}}

И, наконец, верните этот полный словарь, чтобы использовать его для добавления следующего значения.


Если вы не знакомы с рекурсивными функциями, я бы порекомендовал попрактиковаться с некоторыми более простыми, а затем написать ту, которая делает то, что вы хотите, но для ввода это только словари.

Общий путь рекурсивной функции только для словаря:

Учитывая путь, это список атрибутов вложенных словарей ([a, b, c, key1] в вашем примере, если c не было списка):

Start (path, value):

If there's only item in your path, build a dictionary setting 
  that key to your value, and you're done.

If there's more than one, build a dictionary using the first 
  element as a key, and set the value as the output of Start(path.remove(0), value)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...