#python #algorithm #parsing #tree #depth-first-search
#python #алгоритм #синтаксический анализ #дерево #поиск в глубину
Вопрос:
итак, я столкнулся с проблемой DFS, когда я пытаюсь создать список путей к файлам из файла JSON (который, очевидно, внутренне преобразуется в dict).
Я попробовал несколько разных подходов, все из которых включают рекурсивный вызов функции, но я не получаю желаемых результатов. Кроме того, у меня возникла проблема с выяснением, как я могу добавить способ хранения этих вложенных иерархий, чтобы они создавали путь для этого родителя.
В конце концов, мне просто нужен список путей, которые будут использоваться для создания каталогов.
Например, если бы у меня был следующий JSON / dict:
input = [
{"root-folder":
{"parent":
{"child": ["leaf", "node", "test"]},
}
},
{"config-folder":
{"user": ["test", "dev", "prod"]}
}
]
Я хотел бы создать следующие каталоги:
root-folder/parent/child/leaf
root-folder/parent/child/node
root-folder/parent/child/test
config-folder/user/test
config-folder/user/dev
config-folder/user/prod
Приведенный ниже код не дает мне ожидаемого результата:
def build_hierarchy_path(directory_hierarchy):
if isinstance(directory_hierarchy, list):
for item in directory_hierarchy:
if isinstance(item, dict):
build_hierarchy_path(item)
elif isinstance(directory_hierarchy, dict):
for key, value in directory_hierarchy.items():
if isinstance(value, dict):
build_hierarchy_path(value)
else:
print(key, value)
Любая помощь очень ценится!
Ответ №1:
Рекурсивный вызов не получает информацию о пути, который вы уже прошли. Вместо печати внутри функции я бы предложил сделать вашу функцию генератором, чтобы она просто выдавала пути. Затем вызывающий может решить, что с ним делать: он может распечатать записи или сделать с ним что-то еще. Это делает вашу функцию менее привязанной к вводу-выводу.
Вот как вы могли бы это сделать:
def build_hierarchy_path(nested):
if isinstance(nested, str):
yield nested
elif isinstance(nested, list):
yield from (path
for value in nested
for path in build_hierarchy_path(value))
elif isinstance(nested, dict):
yield from (key "/" path
for key, value in nested.items()
for path in build_hierarchy_path(value))
else: # Probably a good idea to check for unexpected content
raise ValueError("unexpected type", nested)
Затем вы можете вызвать его следующим образом:
for path in build_hierarchy_path(structure):
print(path)
Ввод здесь может быть таким же простым, как просто строка: в этом случае функция выдает эту строку:
print(*build_hierarchy_path("root"))
Ответ №2:
Если у вас есть возможность изменить свою структуру JSON, я бы рекомендовал использовать более обычное соглашение, в котором содержимое каталога всегда является массивом. Записи в каталоге могут представлять собой простую строку для имени файла или одноклавишный словарь для подкаталога со значением, выраженным в виде каталога (т. Е. Массива).
Это позволило бы каталогам содержать как файлы, так и подкаталоги (с которыми у вашей текущей структуры будут проблемы).
dirs = [
{"root-folder":[
{"parent":[
{"child": ["leaf", "node", "test"]},
"File1",
"File2"]
}]
},
{"config-folder":[
{"user": ["test", "dev", "prod"]},
"File4",
"File5"]
}
]
С помощью этой структуры рекурсивное извлечение путей может быть выполнено с помощью функции генератора:
def getPaths(structure):
for item in structure:
if isinstance(item,str):
yield item
continue
for dirName,content in item.items():
for subPath in getPaths(content):
yield dirName "\" subPath
for path in getPaths(dirs):
print(path)
root-folderparentchildleaf
root-folderparentchildnode
root-folderparentchildtest
root-folderparentFile1
root-folderparentFile2
config-folderusertest
config-folderuserdev
config-folderuserprod
config-folderFile4
config-folderFile5
Если вы не можете изменить структуру входных данных JSON, вам придется обрабатывать несоответствие между верхним уровнем (список словарей) и ветвями (вложенные словари) либо с помощью операторов if / else, либо с помощью отдельных функций.
Вот пример использования двух функций генератора:
dirs = [
{"root-folder":
{"parent":
{"child": ["leaf", "node", "test"]},
}
},
{"config-folder":
{"user": ["test", "dev", "prod"]}
}
]
def getPaths(structure):
if isinstance(structure,list):
for fileName in structure: yield fileName
else:
for dirName,content in structure.items():
for subPath in getPaths(content):
yield dirName "\" subPath
def getPathList(roots):
for rootDir in roots:
for path in getPaths(rootDir):
yield path
for path in getPathList(dirs):
print(path)
root-folderparentchildleaf
root-folderparentchildnode
root-folderparentchildtest
config-folderusertest
config-folderuserdev
config-folderuserprod
Ответ №3:
Редактировать:
Для нового случая нам также необходимо учитывать строки. Здесь мы обрабатываем строки как конечный регистр и печатаем их, в то время как списки и dicts передаются обратно в функцию:
def build_hierarchy_path(directory_hierarchy, parent=None):
if parent is None:
for item in directory_hierarchy:
if isinstance(item, dict):
build_hierarchy_path(item, Path(""))
elif isinstance(directory_hierarchy, str):
print(parent / directory_hierarchy)
elif isinstance(directory_hierarchy, list):
for item in directory_hierarchy:
build_hierarchy_path(item, parent)
elif isinstance(directory_hierarchy, dict):
for key, value in directory_hierarchy.items():
build_hierarchy_path(value, parent / key)
Что дает нам:
root-folderparentchildtemp-childdelete_me
root-folderparentchildleaf
root-folderparentchildnode
root-folderparentchildtest
config-folderusertest
config-folderuserdev
config-folderuserprod
Оригинальный ответ:
Спасибо за предоставление ожидаемого результата здесь. Возможно pathlib
, этот пакет окажется полезным для работы с каталогами.
Я думаю, что для построения вашего кода нам нужно накапливать предыдущие сегменты пути при рекурсивной итерации:
def build_hierarchy_path(directory_hierarchy, parent=None):
if parent is None:
for item in directory_hierarchy:
if isinstance(item, dict):
build_hierarchy_path(item, Path(""))
elif isinstance(directory_hierarchy, dict):
for key, value in directory_hierarchy.items():
if isinstance(value, dict):
build_hierarchy_path(value, parent / key)
else:
for item in value:
print(parent / key / item)
В этом примере мы передаем родительскую структуру пути функции. Это должно дать нам целые пути в печатном выводе:
root-folderparentchildleaf
root-folderparentchildnode
root-folderparentchildtest
config-folderusertest
config-folderuserdev
config-folderuserprod
Комментарии:
1. Привет, Дэвид, большое спасибо за ответ! Похоже, сейчас это работает, однако я сталкиваюсь с проблемой наличия нескольких вложенных иерархий, вызывающих ошибку типа :
TypeError: unsupported operand type(s) for /: 'PosixPath' and 'dict'
. Если у вас есть dict внутри одного из дочерних элементов, скажем, вы хотите, чтобы 1 из папок имел более глубокую иерархию, это нарушает код. Что я имею в виду под этим, используя ваш метод, я могу иметь только список каталогов в любом родительском каталоге, но ни у одного из этих каталогов не может быть дочерних элементов (представленных в виде dicts ).2. Если вы добавите dict в качестве первой записи в списке в любой из иерархий, вы должны получить ту же ошибку.
3. используя это, вы получите эту ошибку, о которой я упоминал выше
python input= [ { "root-folder": { "parent": { "child": [ {"temp-child": "delete_me"}, "leaf", "node", "test" ] } } }, { "config-folder": { "user": [ "test", "dev", "prod" ] } } ]
Ответ №4:
Я создал рекурсивную функцию, которая может обрабатывать довольно сложные структуры (также вложенные каталоги в списках). Рекурсия останавливается, когда все записи в списке являются строками. Вывод представляет собой список путей, которые в дальнейшем могут быть использованы для построения желаемых иерархий каталогов.
Функции для извлечения путей к каталогам:
# Helper functions
def sort_list_by_type(l):
#
strings = []
dics = []
for entry in l:
if isinstance(entry, str):
strings = [entry]
elif isinstance(entry, dict):
dics = [entry]
return strings dics
def build_paths(dic, root, paths):
#
for key,val in dic.items():
if isinstance(val, dict):
root = f'{key}/'
return build_paths(val, root, paths)
elif isinstance(val, list):
val = sort_list_by_type(val)
for item in val:
if isinstance(item, str):
paths = f'{root}{key}/{item} '
elif isinstance(item, dict):
root = f'{key}/'
return build_paths(item, root, paths)
return paths
# Main
def get_paths(dic_list:'list of dictionaries'):
#
paths = ''
for entry in dic_list:
paths = build_paths(entry, '', '')
return paths.split(' ')[:-1]
Примеры
# Yours
inputs = [
{"root-folder":
{"parent":
{"child": ["leaf", "node", "test"]},
}
},
{"config-folder":
{"user": ["test", "dev", "prod"]}
}
]
get_paths(inputs)
>>> ['root-folder/parent/child/leaf',
'root-folder/parent/child/node',
'root-folder/parent/child/test',
'config-folder/user/test',
'config-folder/user/dev',
'config-folder/user/prod']
# Other (complex structures)
inputs = [
{"root-folder":
{"parent":
{"child": [
"leaf", "node", "test",{'sub-child':
{'templates':[
'A',
{'sub-templates':[
'1','2',
{'temporary':['a','b']}
]},
'B'
]}
},
'etc'
]}
}
},
{"config-folder":{"user": [
"test", "dev", "prod"
]}
}
]
get_paths(inputs)
>>> ['root-folder/parent/child/leaf',
'root-folder/parent/child/node',
'root-folder/parent/child/test',
'root-folder/parent/child/etc',
'root-folder/parent/child/sub-child/templates/A',
'root-folder/parent/child/sub-child/templates/B',
'root-folder/parent/child/sub-child/templates/sub-templates/1',
'root-folder/parent/child/sub-child/templates/sub-templates/2',
'root-folder/parent/child/sub-child/templates/sub-templates/temporary/a',
'root-folder/parent/child/sub-child/templates/sub-templates/temporary/b',
'config-folder/user/test',
'config-folder/user/dev',
'config-folder/user/prod']
В сочетании с построением иерархий каталогов
import os
# Helper
def create_dirs(path):
#
build_path = '/'
for parent in path.split('/'):
build_path = f'{parent}/'
if not os.path.exists(build_path):
os.mkdir(build_path)
# Main
def build_directory_hierarchy(root_dir:'abs path (str), must exist!', list_dir:list):
#
if not os.path.exists(root_dir):
raise ValueError("Root directory doesn't exist.")
for dir_path in list_dir:
dir_path = root_dir dir_path if root_dir.endswith('/') else f'{root_dir}/{dir_path}'
create_dirs(dir_path)
# Use case
build_directory_hierarchy('/PATH_TO_HOME/desktop/test', get_paths(inputs))
>>> "Directory hierarchy successfully created in 'test' folder."
Примечание: корневой путь к рабочему каталогу (‘test’ в случае использования) должен существовать. В противном случае возникает ошибка.
С нетерпением ждем критики или рекомендаций и с удовольствием выслушаем ваши комментарии о вариантах использования.