递归.reduce()输出父数组

时间:2018-05-24 16:29:50

标签: javascript recursion reduce

我有一个像这样的平面文件夹:

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"}]

我想输出给定文件夹的所有父项的数组,以生成类似于面包屑的文件夹路径组件。

我现在有一个代码可以满足我的需求,但是我想在更多功能上更好地编写代码#34;方式,递归使用reduce。

如果我这样做:

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

然后我记录输出,我可以看到它成功找到第一个Parent,然后重新执行代码,并输出父亲的...因为我的初始数组在每一步逻辑上重置为[]。 ..虽然找不到办法...

3 个答案:

答案 0 :(得分:0)

你正在以一种倒退的方式思考它。您有一个folder作为输入,并且您希望扩展许多文件夹的痕迹导览列表。这实际上与reduce相反,unfold将多个值作为输入,并返回单个值。

Reduce也称为 fold ,折叠的反面是展开f接受循环函数initnext状态。我们的函数给出了循环控制器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




上面,我们从一个数字的初始状态开始,unfold在这种情况下。就像reduce中的accumulator变量一样,您可以将任何初始状态指定为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




如果您跟踪上面的计算,您会看到在展开过程中添加到outupt的每个文件夹f调用一次data。这是一项昂贵的操作,如果您的Map集非常大,可能会对您造成问题。

更好的解决方案是创建一个具有更适合此类查询的结构的数据的附加表示。如果您只是创建f.id -> f unfold,则可以将查找时间从线性减少到对数。

Dim percent As Integer Function ADJUST(percent) If percent > 0 Then percent = (percent + 100) * 0.01 'Converts value to percentage to increase percent.Copy Range("K4:N20").PasteSpecial Paste:=xlPasteAll, Operation:=xlMultiply Else percent = 0 End If End Function 非常强大,适用于各种各样的问题。我many other answers以各种方式依赖它。在那里甚至还有一些处理异步的事情。

如果您遇到困难,请不要犹豫,提出后续问题:D

答案 1 :(得分:0)

您可以使用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)) : []);

答案 2 :(得分:0)

你可以用一个相当难看的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])