【问题标题】:Create a tree from multiple nested dictionaries/lists in Python从 Python 中的多个嵌套字典/列表创建树
【发布时间】:2016-12-07 06:32:49
【问题描述】:

前言:为了帮助解释为什么我这样做,我将解释最终目标。本质上,我有一个 accounts 列表,它们以非常特定的语法定义。以下是一些示例:

Assets:Bank:Car
Assets:Bank:House
Assets:Savings:Emergency
Assets:Savings:Goals:Roof
Assets:Reserved

如上所示,一个帐户可以有任意数量的父母和子女。最终目标是将上述帐户解析为 Python 中的树结构,该树结构将用于在 Sublime 文本编辑器中提供帐户自动完成功能(即,如果我输入 Assets: 然后查询 auto -完成,我会看到这样的列表:银行,储蓄,保留

结果:使用前言中的帐户列表,Python 中所需的结果如下所示:

[  
   {  
      "Assets":[  
         {  
            "Bank":[  
               "Car",
               "House"
            ]
         },
         {  
            "Savings":[  
               "Emergency",
               {  
                  "Goals":[  
                     "Roof"
                  ]
               }
            ]
         },
         "Reserved"
      ]
   }
]

半解决方案:我能够使用递归将两个基本帐户加在一起。这适用于添加这两个:Assets:Bank:CarAssets:Bank:House。但是,一旦它们开始不同,它就会开始分崩离析并且递归变得混乱,所以我不确定这是否是最好的方法。

import re

def parse_account(account_str):
    subs = account_str.split(":")

    def separate(subs):
        if len(subs) == 1:
            return subs
        elif len(subs):
            return [{subs[0]: separate(subs[1:])}]

    return separate(subs)

def merge_dicts(a, b):
    # a will be a list with dictionaries and text values and then nested lists/dictionaries/text values
    # b will always be a list with ONE dictionary or text value

    key = b[0].keys()[0] # this is the dictionary key of the only dictionary in the b list

    for item in a: # item is a dictionary or a text value
        if isinstance(item, dict): # if item is a dictionary
            if key in item:
                # Is the value a list with a dict or a list with a text value
                if isinstance(b[0][key][0], str):
                    # Extend the current list with the new value
                    item[key].extend(b[0][key])
                else:
                    # Recurse to the next child
                    merge_dicts(item[key], b[0][key])
            else:


    return a

# Accounts have an "open [name]" syntax for defining them
text = "open Assets:Bank:Car\nopen Assets:Bank:House\nopen Assets:Savings:Emergency\nopen Assets:Savings:Goals:Roof\nopen Assets:Reserved"
EXP = re.compile("open (.*)")
accounts = EXP.findall(text) # This grabs all accounts

# Create a list of all the parsed accounts
dicts = []
for account in accounts:
    dicts.append(parse_account(account))

# Attempt to merge two accounts together
final = merge_dicts(dicts[0], dicts[1])
print final

# In the future we would call: reduce(merge_dicts, dicts) to merge all accounts

我可能会以完全错误的方式处理这件事,我会对不同的意见感兴趣。否则,是否有人了解如何使用示例字符串中的剩余帐户进行此操作?

【问题讨论】:

  • 你想要的输出会给出一个SyntaxErrorReserved 是一个没有值的键)。
  • 抱歉,我将输出粘贴为:print json.dumps(final),以使其更易于识别。

标签: python dictionary recursion tree nested


【解决方案1】:

我花了很长时间才在脑海中整理出来。字典很简单,一个键总是有一个列表作为值 - 它们习惯于有一个命名列表。

列表内将是一个字符串,或另一个字典(带有一个列表的键)。

这意味着我们可以分解“Assets:Bank:Car”并在根列表中查找与{"Assets":[<whatever>]} 匹配的字典或添加一个 - 然后跳转到更深两层的[<whatever>] 列表。下一个循环,查找匹配{"Bank":[<whatever>]} 的字典,或者添加一个,跳转到[<whatever>] 列表更深两级。继续这样做,直到我们到达最后一个节点Car。我们必须在 a 列表中,因为我们总是跳转到现有列表或创建新列表,因此将 Car 放入当前列表中。

注意。如果你有这种方法会失败

Assets:Reserved
Assets:Reserved:Painting

但那将是一个无意义的冲突输入,要求“保留”既是叶节点又是容器,在这种情况下你只会有:

Assets:Reserved:Painting

对吗?

data = """
Assets:Bank:Car
Assets:Bank:House
Assets:Savings:Emergency
Assets:Savings:Goals:Roof
Assets:Reserved
"""
J = []

for line in data.split('\n'):
    if not line: continue

    # split the line into parts, start at the root list
    # is there a dict here for this part?
    #   yes? cool, dive into it for the next loop iteration
    #   no? add one, with a list, ready for the next loop iteration
    #    (unless we're at the final part, then stick it in the list 
    #     we made/found in the previous loop iteration)

    parts = line.split(':')
    parent_list, current_list = J, J

    for index, part in enumerate(parts):
        for item in current_list:
            if part in item:
                parent_list, current_list = current_list, item[part]
                break
        else:
            if index == len(parts) - 1:
                # leaf node, add part as string
                current_list.append(part)
            else:
                new_list = []
                current_list.append({part:new_list})
                parent_list, current_list = current_list, new_list      

print J

->

[{'Assets': [{'Bank': ['Car', 'House']}, {'Savings': ['Emergency', {'Goals': ['Roof']}]}, 'Reserved']}]

在线试用:https://repl.it/Ci5L

【讨论】:

  • 谢谢!实际上,我最终得到了一个可行的解决方案,但这比我的实现要短一点。另外,关于您关于叶节点和容器的问题,是的,以这种方式定义帐户是无效的。
猜你喜欢
  • 2022-07-27
  • 1970-01-01
  • 2020-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多