Самый эффективный способ найти пути подкаталогов - PullRequest
2 голосов
/ 25 октября 2019

Допустим, у меня есть следующая структура каталогов

PROJECT
|
+ BUILD
|    |
|    + STH1
|    |    |
|    |    + 6.11.2
|    |    |
|    |    + 6.11.3
|    |    |
|    |    + .....
|    + STH2
|    |    |
|    |    + 6.11.2
|    |    |
|    |    + 6.11.3
|    |    |
|    |    + .....
+ COMMON
|    |
|    + 6.11.2
|    |
|    + ....

Какой самый эффективный способ найти все каталоги 6.11.2 в каталоге PROJECT в python?

Я пытался использовать

glob.glob('PROJECT/**/6.11.2', recursive=True)

. Это работает, но это не лучшее решение для меня, потому что оно также будет искать каталоги, такие как

PROJECT/BUILD/STH1/6.11.2/6.11.2

Так что это занимает много времени, янужно только найти первое вхождение каталога 6.11.2, поэтому более глубокий поиск в нем - пустая трата времени (в моей структуре такой ситуации, как sth / 6.11.2 / 6.11.2, не будет)

Есть ли лучший способ сделать поиск в Python?

Обратите внимание, что 6.11.2 также является довольно сложным каталогом с большим количеством файлов и подкаталогов, поэтому рекурсивный поиск занимает много времени.

Ответы [ 2 ]

1 голос
/ 25 октября 2019

Если я правильно понимаю, вы не хотите находить каталоги с таким же базовым именем , это так? Если да, то это должно сработать:

import os
from collections import deque
from typing import List, Set


def scandir_only_dirs(path: str) -> List[str]:
    return [f.path for f in os.scandir(path) if f.is_dir()]


def scandir_no_same_basename(path: str) -> Set[str]:
    result = set()
    queue = deque(scandir_only_dirs(path))

    if not queue:
        return result

    visited_basenames = set()

    while queue:
        currdir = queue.popleft()
        basename = os.path.basename(currdir)
        if basename not in visited_basenames:
            result.add(currdir)
            queue.extendleft(scandir_only_dirs(currdir))
            visited_basenames.add(basename)

    return result

В вашем примере дерева каталогов эта функция возвращает:

{'.\\build',
 '.\\build\\sth1',
 '.\\build\\sth2',
 '.\\build\\sth2\\6.11.2',
 '.\\build\\sth2\\6.11.3',
 '.\\common'}

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

Изменить

Добавление ответа ниже, потому что я неправильно понял вопрос:

def find_paths_to_dir(dir_basename: str, from_path: str=".") -> Set[str]:
    result = set()
    queue = deque(scandir_only_dirs(from_path))

    if not queue:
        return result

    while queue:
        currdir = queue.popleft()
        basename = os.path.basename(currdir)
        if basename == dir_basename:
            result.add(currdir)
        else:
            queue.extendleft(scandir_only_dirs(currdir))

    return result
0 голосов
/ 25 октября 2019

Замедление, вероятно, связано с сопоставлением с файлами, превышающими вложенные каталоги 6.11.2. Посмотрите, поможет ли добавление / к вашему шаблону игнорировать файлы.

Также попробуйте использовать версию итератора iglob , которая сэкономит glob, поместив все в список.

filter(lambda p: p.count('6.11.2') == 1, glob.iglob('PROJECT/**/6.11.2/', recursive=True))

В противном случае вы всегда можете написать собственный обход каталога с помощью os.scandir .

...