Рекурсивная функция для извлечения файлов из вложенного JSON-подобного объекта - PullRequest
2 голосов
/ 31 мая 2019

У меня есть данные, которые состоят из имени каталога и его файлов.Учитывая конкретное имя каталога в аргументе скажем, мне нужно найти все файлы, которые он содержит.мои данные:

var fileData = {
      dir : 'app',
      files : [
        'index.html',
        {
          dir : 'js',
          files: [
            'main.js',
            'app.js',
            'misc.js',
            {
              dir : 'vendor',
              files : [
                'jquery.js',
                'underscore.js'
              ]
            }
          ]
        },
        {
          dir : 'css',
          files : [
            'reset.css',
            'main.css'
          ]
        }
      ]
    };

У меня есть список файлов функций, где я передаю имя каталога

listFiles(fileData, 'js')

Ожидаемый вывод:

['main.js','app.js','misc.js','jquery.js','underscore.js']

В моем коде ясмог получить ответ в консоли, но не смог вернуть значение.

listFiles = (data, dirName) => {
          var filesArray = [];
          matchedDir = (data,dirName) => {
            data['files'].map(el => {
                if(typeof(el) === 'object'){
                  return matchedDir(el,dirName)
                }
                else{filesArray.push(el)}
              })
              console.log(filesArray)
            }
          if(Object.values(data).includes(dirName)) {
            return matchedDir(data,dirName)
          }
          else{
            data['files'].map(el => {
              if(typeof(el) === 'object'){
                return this.listFiles(el,dirName)
              }
            })
          }
        return filesArray
}

Ответы [ 2 ]

1 голос
/ 31 мая 2019

Вот разложенный подход к проблеме с использованием функциональных приемов.

Сначала реализуйте общую функцию files -

const emptyTree = 
  { dir: "", files: [] }

const files = (tree = emptyTree, path = "") =>
  Object(tree) === tree
    ? tree.files.flatMap(f => files(f, `${path}/${tree.dir}`))
    : [ `${path}/${tree}` ]

files(fileData)
// [ "/app/index.html"
// , "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// , "/app/css/reset.css"
// , "/app/css/main.css"
// ]

Затем реализуйте search как функцию более высокого порядка, например Array.prototype.filter -

const identity = x =>
  x

const search = (test = identity, tree = emptyTree) =>
  files(tree).filter(test)

Наконец, мы можем использовать search интуитивно понятным способом -

search(f => f.endsWith(".js"), fileData)
// [ "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// ]

search(f => f.startsWith("/app/css"), fileData)
// [ "/app/css/reset.css"
// , "/app/css/main.css"
// ]

Разделив сложную проблему на изолированные части, каждую функцию проще писать, тестировать и обслуживать. Дополнительным преимуществом является то, что files и search предлагают даже больше функциональных возможностей, чем ваша первоначальная функция, и их можно легче повторно использовать в других областях вашей программы. Надеемся, что это демонстрирует, как функции более высокого порядка дают вам повышенную гибкость и с меньшим количеством кода тоже.

Запустите полную программу в своем браузере, расширив фрагмент кода ниже -

const fileData = 
  { dir: 'app', files: [ 'index.html', { dir: 'js', files: [ 'main.js','app.js','misc.js', { dir: 'vendor', files: [ 'jquery.js','underscore.js' ] } ] }, { dir: 'css', files: [ 'reset.css','main.css' ] } ] }

const emptyTree = 
  { dir: "", files: [] }

const files = (tree = emptyTree, path = "") =>
  Object(tree) === tree
    ? tree.files.flatMap(f => files(f, `${path}/${tree.dir}`))
    : [ `${path}/${tree}` ]

console.log(files(fileData))
// [ "/app/index.html"
// , "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// , "/app/css/reset.css"
// , "/app/css/main.css"
// ]

const identity = x =>
  x

const search = (test = identity, tree = emptyTree) =>
  files(tree).filter(test)


console.log(search(f => f.endsWith(".js"), fileData))
// [ "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// ]

console.log(search(f => f.startsWith("/app/css"), fileData))
// [ "/app/css/reset.css"
// , "/app/css/main.css"
// ]

Другой подход заключается в использовании генераторов. Обратите внимание на сходство между этой и предыдущей программами -

const files = function* (tree = emptyTree, path = "")
{ if (Object(tree) === tree)
    for (const f of tree.files)  
      yield* files(f, `${path}/${tree.dir}`)
  else
    yield `${path}/${tree}`
}

const search = function* (test = identity, tree = emptyTree)
{ for (const f of files(tree))
    if (test(f))
      yield f
}

Теперь files и search возвращают ленивый результат, где результаты могут обрабатываться один за другим, когда они выходят из генератора. Или мы можем собрать все результаты, используя Array.from. Результаты одинаковы -

Array.from(search(f => f.endsWith(".js"), fileData))
// [ "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// ]

Array.from(search(f => f.startsWith("/app/css"), fileData))
// [ "/app/css/reset.css"
// , "/app/css/main.css"
// ]

Поскольку генераторы могут приостанавливать и возобновлять работу, эта программа имеет дополнительное преимущество, заключающееся в том, что вычисления могут быть остановлены на ранней стадии. Напротив, первая программа, использующая Array.prototype.flatMap и Array.prototype.filter, всегда будет перебирать все tree.

Разверните фрагмент ниже, чтобы проверить результаты в вашем собственном браузере

const fileData = 
  { dir: 'app', files: [ 'index.html', { dir: 'js', files: [ 'main.js','app.js','misc.js', { dir: 'vendor', files: [ 'jquery.js','underscore.js' ] } ] }, { dir: 'css', files: [ 'reset.css','main.css' ] } ] }

const emptyTree = 
  { dir: "", files: [] }


const files = function* (tree = emptyTree, path = "")
{ if (Object(tree) === tree)
    for (const f of tree.files)  
      yield* files(f, `${path}/${tree.dir}`)
  else
    yield `${path}/${tree}`
}

console.log(Array.from(files(fileData)))
// [ "/app/index.html"
// , "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// , "/app/css/reset.css"
// , "/app/css/main.css"
// ]

const identity = x =>
  x

const search = function* (test = identity, tree = emptyTree)
{ for (const f of files(tree))
    if (test(f))
      yield f
}

console.log(Array.from(search(f => f.endsWith(".js"), fileData)))
// [ "/app/js/main.js"
// , "/app/js/app.js"
// , "/app/js/misc.js"
// , "/app/js/vendor/jquery.js"
// , "/app/js/vendor/underscore.js"
// ]

console.log(Array.from(search(f => f.startsWith("/app/css"), fileData)))
// [ "/app/css/reset.css"
// , "/app/css/main.css"
// ]
1 голос
/ 31 мая 2019

Создайте массив результатов при первом вызове функции и передайте этот массив при каждом рекурсивном вызове:

const getAllFilesFromDirectory = ({ dir, files }, dirToFind, result = [], parentMatch = false) => {
  const addItemsThisDirectory = parentMatch || dir === dirToFind;
  files.forEach((fileOrDir) => {
    if (typeof fileOrDir === 'string') {
      if (addItemsThisDirectory) {
        result.push(fileOrDir);
      }
    } else {
      getAllFilesFromDirectory(fileOrDir, dirToFind, result, addItemsThisDirectory);
    }
  });
  return result;
};

var fileData = {
  dir: 'app',
  files: [
    'index.html',
    {
      dir: 'js',
      files: [
        'main.js',
        'app.js',
        'misc.js',
        {
          dir: 'vendor',
          files: [
            'jquery.js',
            'underscore.js'
          ]
        }
      ]
    },
    {
      dir: 'css',
      files: [
        'reset.css',
        'main.css'
      ]
    }
  ]
};
console.log(getAllFilesFromDirectory(fileData, 'js'));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...