Рекурсивный поиск в подкаталогах для указанного имени каталога в узле - PullRequest
0 голосов
/ 26 мая 2020

У меня есть такая структура каталогов:

/git
    /content
        /repo1
        /repo2
        /repo3
    /modules
        /repo4
        /repo5
    /tools
        /project
            /repo6
            /repo7
        /test
            /repo8
            /repo9

Я хотел бы иметь возможность найти путь к определенному репо, просто передав имя репо:

searchDirForSubdir('/git', 'repo7'); // expected to return /git/tools/project/repo7

Функция, которая у меня есть на данный момент (ниже), возвращает undefined, хотя вызов console.log выдает правильный путь. Я знаю, что испортил рекурсию, но не могу понять, что делаю неправильно.

function searchDirForSubdir (dirToSearch, needle, depth = 0) {
    const DEPTH_LIMIT = 4;
    const fs = require('fs');
    for (let entry of fs.readdirSync(dirToSearch)) {
        if (depth + 1 <= DEPTH_LIMIT) {
            let fullPath = `${dirToSearch}/${entry}`;
            if (!entry.startsWith('.')
                && fs.lstatSync(fullPath).isDirectory()
            ) {
                if (entry == needle) {
                    console.log(fullPath);
                    return fullPath;
                } else {
                    searchDirForSubdir (fullPath, needle, depth + 1);
                }
            }
        }
    }
}

Ответы [ 2 ]

1 голос
/ 26 мая 2020

вам не хватает предложения return перед строкой searchDirForSubdir (fullPath, needle, depth + 1);, если он что-то вернул.

Ваш код исправлен:

function searchDirForSubdir(dirToSearch, needle, depth = 0) {
    const DEPTH_LIMIT = 4;
    const fs = require('fs');
    for (let entry of fs.readdirSync(dirToSearch)) {
        if (depth + 1 <= DEPTH_LIMIT) {
            let fullPath = `${dirToSearch}/${entry}`;
            if (!entry.startsWith('.')
                 && fs.lstatSync(fullPath).isDirectory()) {
                if (entry == needle) {
                    return fullPath;
                } else {
                    const found = searchDirForSubdir(fullPath, needle, depth + 1);
                    if (found)
                        return found;
                }
            }
        }
    }
}
0 голосов
/ 27 мая 2020

fs / promises и fs.Dirent

Рекурсия - это функциональное наследие, поэтому использование ее в функциональном стиле дает наилучшие результаты. Вот эффективная программа dirs, использующая быстрые fs.Dirent объекты Node и модуль fs/promises. Такой подход позволяет пропустить бесполезные вызовы fs.exist или fs.stat на каждом пути -

import { readdir } from "fs/promises"
import { join } from "path"

async function* dirs (path = ".")
{ yield path
  for (const dirent of await readdir(path, { withFileTypes: true }))
    if (dirent.isDirectory())
      yield* dirs(join(path, dirent.name))
}

async function* empty () {}

async function toArray (iter = empty())
{ let r = []
  for await (const x of iter)
    r.push(x)
  return r
}

toArray(dirs(".")).then(console.log, console.error)

Давайте возьмем несколько файлов, чтобы мы могли видеть dirs работающие -

$ yarn add immutable     # (just some example package)
$ node main.js
[
  '.',
  'node_modules',
  'node_modules/immutable',
  'node_modules/immutable/contrib',
  'node_modules/immutable/contrib/cursor',
  'node_modules/immutable/contrib/cursor/__tests__',
  'node_modules/immutable/dist'
]

asyn c генераторы

И поскольку мы используем asyn c генераторы , мы можем интуитивно остановить итерацию, как только найден соответствующий файл -

import { readdir } from "fs/promises"
import { join, basename } from "path"

async function* dirs // ...

async function* empty // ...

async function toArray // ...

async function search (iter = empty(), test = _ => false)
{ for await (const p of iter)
    if (test(p))
      return p   // <-- iteration stops here
}

search(dirs("."), path => basename(path) === "contrib") // <-- search for contrib
  .then(console.log, console.error)

search(dirs("."), path => basename(path) === "foobar") // <-- search for foobar
  .then(console.log, console.error)
$ node main.js
node_modules/immutable/contrib      # ("contrib" found)
undefined                           # ("foobar" not found)

изобретайте для себя

Выше search - это функция высшего порядка, например Array.prototype.find . Мы могли бы написать searchByName, что, вероятно, более удобно использовать -

import // ...

async function* dirs // ...

async function* empty // ...

async function toArray // ...

async function search // ...

async function searchByName (iter = empty(), query = "")
{ return search(iter, p => basename(p) === query) }

searchByName(dirs("."), "contrib")
  .then(console.log, console.error)

searchByName(dirs("."), "foobar")
  .then(console.log, console.error)

Вывод такой же -

$ node main.js
node_modules/immutable/contrib      # ("contrib" found)
undefined                           # ("foobar" not found)

сделать его модулем

Практика, которой уделяется недостаточно внимания. Создавая модуль , мы создаем место для разделения задач и не позволяем сложности перегрузить остальную часть нашей программы -

// FsExtensions.js

import { readdir } from "fs/promises"  // <-- import only what you need
import { join, basename } from "path"

async function* dirs // ...

async function* empty // ...

async function search // ...

async function searchByName // ...

async function toArray // ...

// ...

export { dirs, search, searchByName, toArray } // <-- you control what to export
// Main.js

import { dirs, searchByName } from './FsExtensions' // <-- import only what's used

searchByName(dirs("."), "contrib")
  .then(console.log, console.error)

searchByName(dirs("."), "foobar")
  .then(console.log, console.error)

ограничение глубины

dirs реализуется с помощью рекурсивного генератора. Мы можем легко ограничить глубину рекурсии, добавив параметр в нашу функцию. Я использую значение по умолчанию Infinity, но вы можете выбрать все, что захотите -

async function* dirs (path = "."<b>, depth = Infinity)</b>
{ <b>if (depth < 1) return</b> // stop if depth limit is reached
  yield path
  for (const dirent of await readdir(path, { withFileTypes: true }))
    if (dirent.isDirectory())
      yield* dirs(join(path, dirent.name)<b>, depth - 1</b>)
}

Когда dirs вызывается со вторым аргументом, глубина рекурсии ограничена -

toArray(dirs(".", 1)).then(console.log, console.error)
// [ '.' ]

toArray(dirs(".", 2)).then(console.log, console.error)
// [ '.', 'node_modules' ]

toArray(dirs(".", 3)).then(console.log, console.error)
// [ '.', 'node_modules', 'node_modules/immutable' ]

toArray(dirs(".", 4)).then(console.log, console.error)
// [ '.',
//   'node_modules',
//   'node_modules/immutable',
//   'node_modules/immutable/contrib',
//   'node_modules/immutable/dist'
// ]
searchByName(dirs(".", 1), "contrib").then(console.log, console.error)
// undefined

searchByName(dirs(".", 2), "contrib").then(console.log, console.error)
// undefined

searchByName(dirs(".", 3), "contrib").then(console.log, console.error)
// undefined

searchByName(dirs(".", 4), "contrib").then(console.log, console.error)
// node_modules/immutable/contrib

searchByName(dirs("."), "contrib").then(console.log, console.error)
// node_modules/immutable/contrib

searchByName(dirs("."), "foobar").then(console.log, console.error)
// undefined
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...