【问题标题】:Python: create a nested dict from strings of key:value pairsPython:从键值对的字符串创建嵌套字典
【发布时间】:2018-04-30 12:35:42
【问题描述】:

我有一个这样的字典:

{"key1:key2[0]:key3[0]": "1234",
 "key1:key2[0]:key4[0]:key5": "4567",
 "key1:key2[1]:key3[0]": "789",
 "key1:key2[1]:key4[1]:key5": "12345"}

键是表示目标字典中每个最终值的沿袭的描述性方式。 : 将父键与其子键分开,[] 表示前键的值是一个列表,并且索引存在于大括号之间。

鉴于此,我该如何构造一个像

{
   "key1":{
      "key2":[
         {
            "key3":["1234"],
            "key4":[{"key5":"4567"}]
         },
         {
            "key3":["789"],
            "key4":[{"key5":"12345"}]
         }
      ]
   }
}

我试图做这样的事情:

result_dict = {}

def populate(target_path, value):
    current_point_in_path = None
    t = result_dict
    target_path = target_path.split(":")
    for i, each_key in enumerate(target_path):
        list_index = re.findall(r'\[(.*?)\]', each_key)
        if len(list_index) > 1:
            raise Exception("not allowed")
        elif len(list_index) == 1:
            index = int(list_index[0])
            key_before = each_key.split(index)[0]
            if not isinstance(result_dict[key_before], list):
                t = t.setdefault(key_before, [])
                if i+1 == len(target_path):
                    # the issue is that this insert won't return a pointer to the current index element like setdefault would do
                    # alternate soultions are wc
                    t.insert(index, value)
                else:
                    t.insert(index, {})

        else:
            if i + 1 == len(target_path):
                t = t.setdefault(each_key, value)
            else:
                t = t.setdefault(each_key, {})

我无法在这里完成部分代码。也许我需要用我的描述性语言进行更好的设计。欢迎提出任何建议。

【问题讨论】:

  • “也许我需要用我的描述性语言进行更好的设计。欢迎提出任何建议。”源字典的数据从哪里来?因为如果可以的话,我建议你使用 JSON 标准来存储这种多级数据结构。 docs.python.org/2/library/json.html 使用 JSON,很容易转储(序列化)和加载(反序列化)这样的结构。
  • 所以我的意思是,如果您可以将输入数据结构更改为另一种结构,我建议您使用 JSON。那么将JSON字符串的“解码”部分变成多级字典就很简单了:json.loads(json_string)! :)

标签: python list dictionary multidimensional-array


【解决方案1】:

您可以使用itertools.groupby 进行递归:

import re, itertools
d = {"key1:key2[0]:key3": "1234", "key1:key2[0]:key4": "4567", "key1:key2[1]:key3": "789", "key1:key2[1]:key4": "12345"}
new_d = [(re.findall('\w+', a), b) for a, b in d.items()]
def last_group(d):
  return [{a[-1]:c for a, c in list(b)} for _, b in itertools.groupby(sorted(d, key=lambda x:x[0][1]), key=lambda x:x[0][1])]

def group_data(d):
   return {a:(lambda x:group_data([(c[1:], d) for c, d in x]) if all(len(c) > 3 for c, _ in x) else last_group(x))(list(b)) for a, b in itertools.groupby(sorted(d, key=lambda x:x[0][0]), key=lambda x:x[0][0])}

print(group_data(new_d))

输出:

{'key1': {'key2': [{'key3': '1234', 'key4': '4567'}, {'key3': '789', 'key4': '12345'}]}}

【讨论】:

  • ValueError: too many values to unpack on return {a:(lambda x:group_data([(c[1:], d) for c, d in x]) if all(len(c) > 3 for c, _ in x) else last_group(x))(list(b)) for a, b in itertools.groupby(sorted(d, key=lambda x:x[0][0]), key=lambda x:x[0][0])}
  • @void 奇怪,它在两个 Python 版本中都适用于我。如果您使用完全不同的输入,请发布。
  • 我的错误。更正了输入,它可以工作。但是如果我有嵌套列表,代码就会中断,就像我添加(编辑)的例子一样。
  • 如果您可以改进答案,我可以将此标记为已接受,因为 itertools 解决方案对我来说看起来更优雅。
【解决方案2】:

你可以使用这个怪物:

def populate(result_dict, target_path, value):
    # split path
    target_path = re.findall(r"[^:]+?(?=\[|:|$)|\[\d+?\]", target_path)
    # prepare path
    for i, element in enumerate(target_path):
        if element[0] == "[" and element[-1] == "]":
            element = int(element[1:-1])
        target_path[i] = element
    current = result_dict
    for i, element in enumerate(target_path[:-1]):
        if isinstance(element, str):  # dict index
            if element not in current:  # create new entry
                if isinstance(target_path[i + 1], str):  # next is a dict
                    current[element] = {}
                else:  # next is a list
                    current[element] = []
        elif isinstance(element, int):  # list index
            if element >= len(current):  # create new entry
                current.extend(None for _ in range(element-len(current)+1))
            if current[element] is None:
                if isinstance(target_path[i + 1], str):  # next is a dict
                    current[element] = {}
                else:  # next is a list
                    current[element] = []
        current = current[element]
    if isinstance(target_path[-1], int):
        current.append(value)
    else:
        current[target_path[-1]] = value

您可以使用此代码来填充字典:

result_dict = {}
for key, value in {"key1:key2[0]:key3[0]": "1234",
                   "key1:key2[0]:key4[0]:key5": "4567",
                   "key1:key2[1]:key3[0]": "789",
                   "key1:key2[1]:key4[1]:key5": "12345"}.items():
    populate(result_dict, key, value)
print(json.dumps(result_dict, indent=4))

打印:

{
    "key1": {
        "key2": [
            {
                "key3": [
                    "1234"
                ],
                "key4": [
                    {
                        "key5": "4567"
                    }
                ]
            },
            {
                "key3": [
                    "789"
                ],
                "key4": [
                    null,
                    {
                        "key5": "12345"
                    }
                ]
            }
        ]
    }
}

【讨论】:

  • 抱歉,您能看一下更新后的示例吗?我希望能够处理嵌套列表。
  • @void 好的,我编辑了,现在可以了。问题是跳过索引key1:key2[1]:key4[0],现在将是None。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-01
  • 2023-01-18
相关资源
最近更新 更多