Построение путей к файлам из файла JSON

#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’ в случае использования) должен существовать. В противном случае возникает ошибка.

С нетерпением ждем критики или рекомендаций и с удовольствием выслушаем ваши комментарии о вариантах использования.