LogoData Structures & Algorithms

Деревья: обход файловой системы (DFS, стек)

Файловая система – дерево: каталоги – узлы, файлы – листья. Обход в глубину (depth-first search, DFS) уходит по пути максимально далеко, используя стек, затем откатывается назад. Подходит для задач вроде: вывести имена всех файлов в каталоге pics, включая подкаталоги.
Сложность:

Время – O(N) по числу записей; память – O(H), где H – максимальная глубина.

walkDirDfs.ts
1import { readdir, stat } from 'node:fs/promises';
2import type { Dirent } from 'node:fs';
3import { join, basename } from 'node:path';
4
5type WalkOptions = {
6 /** Следовать по симлинкам (по умолчанию false) */
7 followSymlinks?: boolean;
8};
9
10/**
11 * Обходит дерево каталогов в глубину (DFS, стек) и возвращает список файлов.
12 *
13 * Требование: вывести имена всех файлов в каталоге `pics`, включая все его подкаталоги.
14 *
15 * Пример:
16 * (async () => {
17 * const files = await walkDirDfs('pics'); // корень обхода
18 * files.forEach(f => console.log(basename(f))); // печать только имён
19 * })().catch(console.error);
20 */
21export async function walkDirDfs(root: string, opts: WalkOptions = {}): Promise<string[]> {
22 const files: string[] = [];
23 const stack: string[] = [root];
24
25 while (stack.length) {
26 const dir = stack.pop()!;
27 let entries: Dirent[];
28 try {
29 entries = await readdir(dir, { withFileTypes: true });
30 } catch {
31 continue; // не каталог / нет доступа – пропускаем
32 }
33
34 for (const entry of entries) {
35 const full = join(dir, entry.name);
36
37 if (entry.isDirectory()) {
38 // Кладём подкаталог в стек; при желании можно сортировать для детерминированного порядка
39 stack.push(full);
40 } else if (entry.isFile()) {
41 files.push(full);
42 } else if (opts.followSymlinks && entry.isSymbolicLink()) {
43 try {
44 const s = await stat(full);
45 if (s.isDirectory()) stack.push(full);
46 else if (s.isFile()) files.push(full);
47 } catch (err) {
48 const code = (err as NodeJS.ErrnoException)?.code;
49 // Игнорируем типичные «безопасные» кейсы симлинков
50 if (code === 'ENOENT' || code === 'EACCES' || code === 'EPERM') {
51 // no-op
52 } else {
53 throw err; // всё остальное – настоящая ошибка
54 }
55 }
56 }
57 }
58 }
59
60 return files;
61}