python - 将文件路径列表转换为类似于 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])
解决方案
该问题与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 只需构建一次。如果你想在之后继续添加额外的路径,我建议保留defaultdict
Trie 的版本。