Python - построение вложенного dict из списка - PullRequest
0 голосов
/ 27 сентября 2018

Я анализирую некоторые дрянные списки в python из api anbd, выводимого во вложенную структуру json с определенным форматом, который будет использоваться тонной интерфейсных сервисов.

Ниже приведен пример, в котором каждый элемент является полным путем к файлу.Я не могу изменить этот вход, поскольку он поступает от внешнего сервиса, который проходит через базу данных.Элементы каталога не отображаются в этом списке, только файлы, каталог, в котором находится файл, является очевидным из пути, то есть, ниже нет файлов MIPK / DORAS.Пример ниже:

"/generic_root/site_store/MIPK/CM.toroidal",
"/generic_root/site_store/MIPK/CM.Supervoid",
"/generic_root/site_store/MIPK/DORAS/CRUDE/CM.forest",
"/generic_root/site_store/MIPK/DORAS/CRUDE/CM.benign",
"/generic_root/site_store/MIPK/DORAS/CRUDE/CM.dunes",
"/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.tangeant",
"/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.astral",
"/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.forking"

Была использована предыдущая функция, которая была невероятно медленной, однако в настоящее время я анализирую список, используя следующий код.Формат вывода не тот формат, который я хочу.Я застреваю при добавлении узла в корень.

В приведенном ниже примере он берет путь, находит вложенные каталоги и удаляет корневой путь, присутствующий в каждом файле, затем создает объект узла, который имеет соответствующую вложенную структуру.

После того, как это добавлено в prev_node, оно добавляется в словарь, используя имя каталога в качестве ключа.

import logging
logger = logging.getLogger(__name__)


def main():
    # Initialise
    root_path    = '/generic_root'
    store        = '/site_store'
    file_list    = [
        "/generic_root/site_store/MIPK/CM.toroidal",
        "/generic_root/site_store/MIPK/CM.Supervoid",
        "/generic_root/site_store/MIPK/DORAS/CRUDE/CM.forest",
        "/generic_root/site_store/MIPK/DORAS/CRUDE/CM.benign",
        "/generic_root/site_store/MIPK/DORAS/CRUDE/CM.dunes",
        "/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.tangeant",
        "/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.astral",
        "/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.forking"
    ]
    # Call loop and display results
    viewstore_tree_map = create_dir_tree(file_list, root_path, store)
    logging.info('\n\tView store keys: %s\n\tKeys: %s\n\tDict of store: %s',
                 len(viewstore_tree_map.keys()), viewstore_tree_map.keys(),
                 viewstore_tree_map)


def joiner(delimiter, *args):
    '''
    Joins path strings correctly, unpack before passing args
    '''
    return delimiter.join(list(args))


def create_dir_tree(file_list, root_path, store):
    '''
    File_list [LIST][STR]
    root_path [STR]
    store [STR]
    Return [DICT]
    '''
    node_map = {}
    full_root = root_path+store
    for sub_path in file_list:
        parents = sub_path.replace(full_root, '').split('/')[1:-1]
        prev_node = None
        node = None
        node_path = full_root
        # create tree structure for directory nodes
        for parent in parents:
            node_path = joiner('/', node_path, parent)
            node_exists = 1
            if node_path not in node_map:
                node_exists = 0
                node_map[node_path] = {
                    'name': parent,
                    'data': [],
                    'type': 'dir',
                    'path': node_path,
                }
            node = node_map[node_path]
            # Handles appending previous dict to data field of new dict
            if prev_node and not node_exists:
                prev_node['data'].append(node)
            prev_node = node
            # logger.info(pprint.pprint(prev_node))

        if node:
            node['data'].append({
                'name': sub_path.rsplit('/')[-1],
                'type': 'file',
                'path': sub_path
            })
    return node_map

Ниже приведен вывод из приведенного выше кода.Это огромно и будет серьезной проблемой с памятью, поскольку их список со временем будет расти.

node_map = {
    '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE': {
        'type': 'dir',
        'data': [{
            'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.tangeant',
            'type': 'file',
            'name': 'CM.tangeant'
        }, {
            'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.astral',
            'type': 'file',
            'name': 'CM.astral'
        }, {
            'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.forking',
            'type': 'file',
            'name': 'CM.forking'
        }],
        'name': 'CRUDE',
        'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE'
    },
    '/generic_root/site_store/MIPK/DORAS/CRUDE': {
        'type': 'dir',
        'data': [{
            'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.forest',
            'type': 'file',
            'name': 'CM.forest'
        }, {
            'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.benign',
            'type': 'file',
            'name': 'CM.benign'
        }, {
            'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.dunes',
            'type': 'file',
            'name': 'CM.dunes'
        }],
        'name': 'CRUDE',
        'path': '/generic_root/site_store/MIPK/DORAS/CRUDE'
    },
    '/generic_root/site_store/MIPK/DORAS/COMMODITIES': {
        'type': 'dir',
        'data': [{
            'type': 'dir',
            'data': [{
                'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.tangeant',
                'type': 'file',
                'name': 'CM.tangeant'
            }, {
                'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.astral',
                'type': 'file',
                'name': 'CM.astral'
            }, {
                'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.forking',
                'type': 'file',
                'name': 'CM.forking'
            }],
            'name': 'CRUDE',
            'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE'
        }],
        'name': 'COMMODITIES',
        'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES'
    },
    '/generic_root/site_store/MIPK': {
        'type': 'dir',
        'data': [{
            'path': '/generic_root/site_store/MIPK/CM.toroidal',
            'type': 'file',
            'name': 'CM.toroidal'
        }, {
            'path': '/generic_root/site_store/MIPK/CM.Supervoid',
            'type': 'file',
            'name': 'CM.Supervoid'
        }, {
            'type': 'dir',
            'data': [{
                'type': 'dir',
                'data': [{
                    'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.forest',
                    'type': 'file',
                    'name': 'CM.forest'
                }, {
                    'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.benign',
                    'type': 'file',
                    'name': 'CM.benign'
                }, {
                    'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.dunes',
                    'type': 'file',
                    'name': 'CM.dunes'
                }],
                'name': 'CRUDE',
                'path': '/generic_root/site_store/MIPK/DORAS/CRUDE'
            }, {
                'type': 'dir',
                'data': [{
                    'type': 'dir',
                    'data': [{
                        'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.tangeant',
                        'type': 'file',
                        'name': 'CM.tangeant'
                    }, {
                        'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.astral',
                        'type': 'file',
                        'name': 'CM.astral'
                    }, {
                        'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.forking',
                        'type': 'file',
                        'name': 'CM.forking'
                    }],
                    'name': 'CRUDE',
                    'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE'
                }],
                'name': 'COMMODITIES',
                'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES'
            }],
            'name': 'DORAS',
            'path': '/generic_root/site_store/MIPK/DORAS'
        }],
        'name': 'MIPK',
        'path': '/generic_root/site_store/MIPK'
    },
    '/generic_root/site_store': {
        'type': 'dir',
        'data': [{
            'type': 'dir',
            'data': [{
                'path': '/generic_root/site_store/MIPK/CM.toroidal',
                'type': 'file',
                'name': 'CM.toroidal'
            }, {
                'path': '/generic_root/site_store/MIPK/CM.Supervoid',
                'type': 'file',
                'name': 'CM.Supervoid'
            }, {
                'type': 'dir',
                'data': [{
                    'type': 'dir',
                    'data': [{
                        'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.forest',
                        'type': 'file',
                        'name': 'CM.forest'
                    }, {
                        'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.benign',
                        'type': 'file',
                        'name': 'CM.benign'
                    }, {
                        'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.dunes',
                        'type': 'file',
                        'name': 'CM.dunes'
                    }],
                    'name': 'CRUDE',
                    'path': '/generic_root/site_store/MIPK/DORAS/CRUDE'
                }, {
                    'type': 'dir',
                    'data': [{
                        'type': 'dir',
                        'data': [{
                            'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.tangeant',
                            'type': 'file',
                            'name': 'CM.tangeant'
                        }, {
                            'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.astral',
                            'type': 'file',
                            'name': 'CM.astral'
                        }, {
                            'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.forking',
                            'type': 'file',
                            'name': 'CM.forking'
                        }],
                        'name': 'CRUDE',
                        'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE'
                    }],
                    'name': 'COMMODITIES',
                    'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES'
                }],
                'name': 'DORAS',
                'path': '/generic_root/site_store/MIPK/DORAS'
            }],
            'name': 'MIPK',
            'path': '/generic_root/site_store/MIPK'
        }],
        'name': 'site_store',
        'path': '/generic_root/site_store'
    },
    '/generic_root/site_store/MIPK/DORAS': {
        'type': 'dir',
        'data': [{
            'type': 'dir',
            'data': [{
                'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.forest',
                'type': 'file',
                'name': 'CM.forest'
            }, {
                'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.benign',
                'type': 'file',
                'name': 'CM.benign'
            }, {
                'path': '/generic_root/site_store/MIPK/DORAS/CRUDE/CM.dunes',
                'type': 'file',
                'name': 'CM.dunes'
            }],
            'name': 'CRUDE',
            'path': '/generic_root/site_store/MIPK/DORAS/CRUDE'
        }, {
            'type': 'dir',
            'data': [{
                'type': 'dir',
                'data': [{
                    'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.tangeant',
                    'type': 'file',
                    'name': 'CM.tangeant'
                }, {
                    'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.astral',
                    'type': 'file',
                    'name': 'CM.astral'
                }, {
                    'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.forking',
                    'type': 'file',
                    'name': 'CM.forking'
                }],
                'name': 'CRUDE',
                'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE'
            }],
            'name': 'COMMODITIES',
            'path': '/generic_root/site_store/MIPK/DORAS/COMMODITIES'
        }],
        'name': 'DORAS',
        'path': '/generic_root/site_store/MIPK/DORAS'
    }
}

2 вопроса:

  1. Как вывести правильный нужный формат?(пример ниже)
  2. Учитывая вышеизложенное, как можно дополнительно оптимизировать функцию по времени для миллионов записей в списке?

 desired output = {
    "type": "dir",
    "data": [{
        "type": "dir",
        "data": [{
            "path": "/generic_root/site_store/MIPK/CM.toroidal",
            "type": "file",
            "name": "CM.toroidal"
        }, {
            "path": "/generic_root/site_store/MIPK/CM.Supervoid",
            "type": "file",
            "name": "CM.Supervoid"
        }, {
            "type": "dir",
            "data": [{
                "type": "dir",
                "data": [{
                    "path": "/generic_root/site_store/MIPK/DORAS/CRUDE/CM.forest",
                    "type": "file",
                    "name": "CM.forest"
                }, {
                    "path": "/generic_root/site_store/MIPK/DORAS/CRUDE/CM.benign",
                    "type": "file",
                    "name": "CM.benign"
                }, {
                    "path": "/generic_root/site_store/MIPK/DORAS/CRUDE/CM.dunes",
                    "type": "file",
                    "name": "CM.dunes"
                }],
                "name": "CRUDE",
                "path": "/generic_root/site_store/MIPK/DORAS/CRUDE"
            }, {
                "type": "dir",
                "data": [{
                    "type": "dir",
                    "data": [{
                        "path": "/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.tangeant",
                        "type": "file",
                        "name": "CM.tangeant"
                    }, {
                        "path": "/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.astral",
                        "type": "file",
                        "name": "CM.astral"
                    }, {
                        "path": "/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.forking",
                        "type": "file",
                        "name": "CM.forking"
                    }],
                    "name": "CRUDE",
                    "path": "/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE"
                }],
                "name": "COMMODITIES",
                "path": "/generic_root/site_store/MIPK/DORAS/COMMODITIES"
            }],
            "name": "DORAS",
            "path": "/generic_root/site_store/MIPK/DORAS"
        }],
        "name": "MIPK",
        "path": "/generic_root/site_store/MIPK"
    }],
    "name": "site_store",
    "path": "/generic_root/site_store"
}

1 Ответ

0 голосов
/ 10 октября 2018

Придумали это решение, которое использует объект навигатора для перемещения вверх и вниз по диктовке.

Сначала разберите строку, разбив ее по пути на список подкаталогов.

Проверьте, существует ли он на текущем уровне результатов (элементы в поле 'data' с 'name' = blah, обработанным lamdba), если не создали его, и переместите навигатор на этот уровень.

После того, как навигатор создал или переместился в правильный подкаталог (последний подкаталог в списке родителей), добавьте объект узла в массив результатов.

Для следующего пути навигатор сбрасывается до полного результата и процесс начинается снова.

Обратная связь приветствуется, я уверен, что она может быть улучшена.

def main():
    # Initialise
    root_path    = '/generic_root'
    store        = '/site_store'
    file_list    = [
        "/generic_root/site_store/MIPK/CM.toroidal",
        "/generic_root/site_store/MIPK/CM.Supervoid",
        "/generic_root/site_store/MIPK/DORAS/CRUDE/CM.forest",
        "/generic_root/site_store/MIPK/DORAS/CRUDE/CM.benign",
        "/generic_root/site_store/MIPK/DORAS/CRUDE/CM.dunes",
        "/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.tangeant",
        "/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.astral",
        "/generic_root/site_store/MIPK/DORAS/COMMODITIES/CRUDE/CM.forking"
    ]
    # Call loop and display results
    viewstore_tree_map = create_dir_tree(file_list, root_path, store)


def Node(full_path, name):
    '''
    full_path[STR] 
    name [STR]
    returns [DICT] node object
    '''
    return {
         'path': full_path,
         'name': name,
         'type': 'dir',
         'data': [],
    }

def Record(full_path):
    '''
    full_path[STR] 
    returns [DICT] record object
    '''
    return {
        'path': full_path,
        'name': full_path.rsplit('/', 1)[-1],
        'type': 'file',
    }

def joiner(delimiter, *args):
    '''
    delimiter[STR] 
    *args [STR]'s unpack sets and lists
    returns [STR] Joins path strings correctly
    '''
    return delimiter.join(list(args))


def create_dir_tree(file_list, root_path, store):
    '''
    file_list [LIST][STR]
    root_path [STR] - root directory
    store [STR] - store site (sub directory)
    returns [DICT] Nested dict of paths from a flat list
    '''
    #  Intiliase
    full_root = self.base_dir + store
    result = Node(full_root, store)
    for path in file_list):
        short_path = path.replace(full_root, '')
        # 1st pass: set navigator to result, 
        # Subsequent: Resets navigator object, and node_path for next path
        navigator = result
        node_path = full_root
        # Remove non-relevant parts of path
        parents = short_path.replace(str_filter, '').split('/')[1:-1]
        for parent in parents:
            node_path = joiner('/', node_path, parent)
            if not filter(lambda dir_rec: dir_rec['name'] == parent,
                          navigator['data']):
                # If node does not exist, append dir object to 'data'
                navigator['data'].append(Node(node_path, parent))
            # Move navigator to previously found node
            navigator = navigator['data'][-1]
        # Append record after constructing or navigating to correct path
        navigator['data'].append(Record(path))
    return result
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...