首页 > 解决方案 > 将文件路径列表转换为类似于 json 文件的嵌套字典

问题描述

我已经尝试了几种在堆栈溢出时发现的解决方案,但是在从字典移动到文件列表时,我要么在分支末尾得到空字典,要么得到文件标记。

问题:一个python列表对象

paths =[/etc/bluetooth/rfcomm.conf.dpkg-remove
/etc/bluetooth/serial.conf.dpkg-remove
/etc/bluetooth/input.conf
/etc/bluetooth/audio.conf.dpkg-remove
/etc/bluetooth/network.conf
/etc/bluetooth/main.conf
/etc/fish
/etc/fish/completions
/etc/fish/completions/task.fish]

expected output:
{"etc" : 
    {"bluetooth" : 
        ["rfcomm.conf.dpkg-remove",
         "serial.conf.dpkg-remove",
         "input.conf",
         "audio.conf.dpkg-remove",
         "network.conf",
         "main.conf"],
     "fish" : 
        {"completions" : "task.fish"}}}

我发现我可以使用以下方法在 javascript 中输出:Parse string of file path to json object

有没有办法使用 dafaultdict 创建没有任何前置文本的叶子列表?

这让我最接近我正在寻找的东西,但我在每个节点都留下了文件标记(defaultdict(dict,((FILE_MARKER,[]),)))。

from collections import defaultdict


FILE_MARKER = '<files>'

def attach(branch, trunk):
    '''
    Insert a branch of directories on its trunk.
    '''
    parts = branch.split('/', 1)
    if len(parts) == 1:  # branch is a file
        trunk[FILE_MARKER].append(parts[0])
    else:
        node, others = parts
        if node not in trunk:
            trunk[node] = defaultdict(dict, ((FILE_MARKER, []),))
        attach(others, trunk[node])

标签: pythonpython-3.x

解决方案


该问题与Trie密切相关。接受以下输入时

paths = [
    "/etc/bluetooth/rfcomm.conf.dpkg-remove",
    "/etc/bluetooth/serial.conf.dpkg-remove",
    "/etc/bluetooth/input.conf",
    "/etc/bluetooth/audio.conf.dpkg-remove",
    "/etc/bluetooth/network.conf",
    "/etc/bluetooth/main.conf",
    "/etc/bluetooth/subdirectory/main.conf",
    "/etc/fish",
    "/etc/fish/completions",
    "/etc/fish/completions/task.fish"
]

我们可以使用以下方法快速构建一个 Trie

from collections import defaultdict
from functools import reduce
from pprint import pprint

# ... your input

if __name__ == '__main__':
    node = lambda: defaultdict(node)
    parts = node()
    
    for path in paths:
        reduce(dict.__getitem__, path[1:].split('/'), parts)
    pprint(parts, width=10)

这导致以下 Trie

defaultdict(<function <lambda> at 0x000001E34CC06E50>,
            {'etc': defaultdict(<function <lambda> at 0x000001E34CC06E50>,
                                {'bluetooth': defaultdict(<function <lambda> at 0x000001E34CC06E50>,
                                                          {'audio.conf.dpkg-remove': defaultdict(<function <lambda> at 0x000001E34CC06E50>, {}),
                                                           'input.conf': defaultdict(<function <lambda> at 0x000001E34CC06E50>, {}),
                                                           'main.conf': defaultdict(<function <lambda> at 0x000001E34CC06E50>, {}),
                                                           'network.conf': defaultdict(<function <lambda> at 0x000001E34CC06E50>, {}),
                                                           'rfcomm.conf.dpkg-remove': defaultdict(<function <lambda> at 0x000001E34CC06E50>, {}),
                                                           'serial.conf.dpkg-remove': defaultdict(<function <lambda> at 0x000001E34CC06E50>, {}),
                                                           'subdirectory': defaultdict(<function <lambda> at 0x000001E34CC06E50>,
                                                                                       {'main.conf': defaultdict(<function <lambda> at 0x000001E34CC06E50>, {})})}),
                                 'fish': defaultdict(<function <lambda> at 0x000001E34CC06E50>,
                                                     {'completions': defaultdict(<function <lambda> at 0x000001E34CC06E50>,
                                                                                 {'task.fish': defaultdict(<function <lambda> at 0x000001E34CC06E50>, {})})})})})

清理

defaultdict现在,您可能希望通过删除Trie 的方面来使其更漂亮,通过以下任一方式

或从中制作普通字典,并带有结束标记,请参见

  • 清理代码

    def simplify(dictionary):
        if isinstance(dictionary, defaultdict):
            return {k: simplify(v) or None for k, v in dictionary.items()}
        return dictionary
    
  • 用法

    pprint(simplify(parts))
    
  • 输出

    {'etc': {'bluetooth': {'audio.conf.dpkg-remove': None,
                           'input.conf': None,
                           'main.conf': None,
                           'network.conf': None,
                           'rfcomm.conf.dpkg-remove': None,
                           'serial.conf.dpkg-remove': None,
                           'subdirectory': {'main.conf': None}},
             'fish': {'completions': {'task.fish': None}}}}
    

限制

我在这里假设它是可以的,通过给它值来指示一个文件None。这样它就可以正确处理子目录并清楚地指示所有结尾。

另一个(隐含的)假设是,在简化时,Trie 只需构建一次。如果你想在之后继续添加额外的路径,我建议保留defaultdictTrie 的版本。


推荐阅读