Рекурсивный .reduce () для вывода массива родителей - PullRequest
0 голосов
/ 24 мая 2018

У меня есть плоский массив папок, подобный этому:

const foldersArray = [{id: "1", parentId: null, name: "folder1"}, {id: "2", parentId: null, name: "folder2"}, {id: "1.1", parentId: 1, name: "folder1.1"}, {id: "1.1.1", parentId: "1.1", name: "folder1.1.1"},{id: "2.1", parentId: 2, name: "folder2.1"}]

Я хочу вывести массив всех родителей данной папки, чтобы сгенерировать Breadcrumb-подобный компонент пути к папке.

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

Если я сделаю:

    getFolderParents(folder){ 
      return this.foldersArray.reduce((all, item) => { 
        if (item.id === folder.parentId) { 
            all.push (item.name) 
            this.getFolderParents(item)
         }
         return all
       }, [])
     }

и я запишу вывод, я вижу, что он успешно находит первого родителя, затем повторно выполняет код и выводит родителя родителя ... как мойисходный массив логически сбрасывается в [] на каждом шаге ... Хотя не могу найти способ обойти ...

Ответы [ 3 ]

0 голосов
/ 24 мая 2018

Вы можете сделать это с Map, чтобы избежать перебора массива каждый раз, когда вам нужно получить следующий родительский элемент.Таким образом, вы получите O (n) вместо O (n²) сложность времени:

const foldersArray = [{id: "1", parentId: null, name: "folder1"}, {id: "2", parentId: null, name: "folder2"}, {id: "1.1", parentId: "1", name: "folder1.1"}, {id: "1.1.1", parentId: "1.1", name: "folder1.1.1"},{id: "2.1", parentId: "2", name: "folder2.1"}];
const folderMap = new Map(foldersArray.map( o => [o.id, o] ));
const getFolderParents = folder => 
    (folder.parentId ? getFolderParents(folderMap.get(folder.parentId)) : [])
    .concat(folder.name);

// Example call:
console.log(getFolderParents(foldersArray[4]));

Небольшое замечание: ваш тип данных parentId не согласован: лучше всегда быть строкой, как и тип данных свойства id.Если нет, вам нужно привести его в свой код, но действительно лучше иметь тип данных с самого начала.Вы заметите, что я определил parentId как строку: это необходимо для работы вышеуказанного кода.В качестве альтернативы, приведите его к строке в коде с помощью String(folder.parentId).

Во-вторых, приведенный выше код будет pre -приставлять имя родительской папки (как это делается в обозначениях файловых папок).Если вам нужно добавить родительское имя после дочернего, то поменяйте concat тему и аргумент:

[folder.name].concat(folder.parentId ? getFolderParents(folderMap.get(folder.parentId)) : []);
0 голосов
/ 24 мая 2018

Вы можете делать то, что ищете, с довольно уродливым циклом while.Получает работу, хотя.Каждый цикл итерации фильтрует, ища экземпляр родителя.Если этого не существует, он останавливается и выходит.Если он существует, он помещает этого родителя в массив tree, устанавливает folder в родительский элемент для перемещения вверх по уровню, а затем переходит к следующей итерации.

const foldersArray = [{
  id: "1",
  parentId: null,
  name: "folder1"
}, {
  id: "2",
  parentId: null,
  name: "folder2"
}, {
  id: "1.1",
  parentId: 1,
  name: "folder1.1"
}, {
  id: "1.1.1",
  parentId: "1.1",
  name: "folder1.1.1"
}, {
  id: "2.1",
  parentId: 2,
  name: "folder2.1"
}]

function getParents(folder){
	const tree = [], storeFolder = folder
  let parentFolder
  while((parentFolder = foldersArray.filter(t => t.id == folder.parentId)[0]) !== undefined){
    tree.push(parentFolder)
    folder = parentFolder
  }
  
  console.log({ originalFolder: storeFolder, parentTree: tree})
}

getParents(foldersArray[3])
0 голосов
/ 24 мая 2018

Ты думаешь об этом задом наперед.У вас есть один folder в качестве ввода, и вы хотите развернуть его до списка «1004 * много папок».Это на самом деле противоположно reduce, которое принимает в качестве входных данных много значений и возвращает одно значение.

Уменьшение также известно как fold , а обратное сгибание равно разворачиваться .unfold принимает циклическую функцию f и состояние init.Наша функция задает контроллеры цикла next, которые добавляют значение к выходу и задает следующее состояние, и done, который сигнализирует об окончании цикла.

const unfold = (f, init) =>
  f ( (value, nextState) => [ value, ...unfold (f, nextState) ]
    , () => []
    , init
    )

const range = (m, n) =>
  unfold
    ( (next, done, state) =>
        state > n
          ? done ()
          : next ( state        // value to add to output
                 , state + 1    // next state
                 )
    , m // initial state
    )

console.log (range (3, 10))
// [ 3, 4, 5, 6, 7, 8, 9, 10 ]

Выше мы начнем с начального состояния числа, в данном случае m.Как и переменная-накопитель в Redu, вы можете указать любое начальное состояние для unfold.Ниже мы выражаем вашу программу, используя unfold.Мы добавляем parent, чтобы упростить выбор родителя папки

const parent = ({ parentId }) =>
  data .find (f => f.id === String (parentId))

const breadcrumb = folder =>
  unfold
    ( (next, done, f) =>
        f == null
          ? done ()
          : next ( f          // add folder to output
                 , parent (f) // loop with parent folder
                 )
    , folder // init state
    )

breadcrumb (data[3])
// [ { id: '1.1.1', parentId: '1.1', name: 'folder1.1.1' }
// , { id: '1.1', parentId: 1, name: 'folder1.1' }
// , { id: '1', parentId: null, name: 'folder1' } ]

breadcrumb (data[4])
// [ { id: '2.1', parentId: 2, name: 'folder2.1' }
// , { id: '2', parentId: null, name: 'folder2' } ]

breadcrumb (data[0])
//  [ { id: '1', parentId: null, name: 'folder1' } ]

Вы можете проверить результаты программы ниже

const data =
  [ {id: "1", parentId: null, name: "folder1"}
  , {id: "2", parentId: null, name: "folder2"}
  , {id: "1.1", parentId: 1, name: "folder1.1"}
  , {id: "1.1.1", parentId: "1.1", name: "folder1.1.1"}
  , {id: "2.1", parentId: 2, name: "folder2.1"}
  ]
  
const unfold = (f, init) =>
  f ( (value, state) => [ value, ...unfold (f, state) ]
    , () => []
    , init
    )

const parent = ({ parentId }) =>
  data .find (f => f.id === String (parentId))

const breadcrumb = folder =>
  unfold
    ( (next, done, f) =>
        f == null
          ? done ()
          : next ( f          // add folder to output
                 , parent (f) // loop with parent folder
                 )
    , folder // init state
    )

console.log (breadcrumb (data[3]))
// [ { id: '1.1.1', parentId: '1.1', name: 'folder1.1.1' }
// , { id: '1.1', parentId: 1, name: 'folder1.1' }
// , { id: '1', parentId: null, name: 'folder1' } ]
   
console.log (breadcrumb (data[4]))
// [ { id: '2.1', parentId: 2, name: 'folder2.1' }
// , { id: '2', parentId: null, name: 'folder2' } ]

console.log (breadcrumb (data[0]))
//  [ { id: '1', parentId: null, name: 'folder1' } ]

Если вы проследите приведенное выше вычисление, вы увидите, что find вызывается один раз для папки f, добавляемой к выходу в процессе развертывания.Это дорогостоящая операция, и если ваш набор data значительно большой, это может стать проблемой для вас.

Лучшим решением будет создание дополнительного представления ваших данных, структура которого лучше подходит дляэтот тип запроса.Если все, что вы делаете, это создаете Map из f.id -> f, вы можете уменьшить время поиска от линейного до логарифмического.

unfold действительно мощный и подходит для широкого спектра задач.У меня есть много других ответов , полагающихся на это различными способами.Там также есть некоторые вопросы, касающиеся асинхронности.

Если вы застряли, не стесняйтесь задавать дополнительные вопросы: D

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...