【问题标题】:Recursive .reduce() to output an array of parents递归 .reduce() 输出父数组
【发布时间】:2018-11-03 23:07:05
【问题描述】:

我有一组这样的文件夹:

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

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

我目前有一个代码可以满足我的需求,但我想以更“功能性”的方式更好地编写它,递归地使用 reduce。

如果我这样做:

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

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

【问题讨论】:

    标签: javascript recursion reduce


    【解决方案1】:

    您可以使用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】:

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

      【讨论】:

        【解决方案3】:

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

        Reduce 也称为 fold,折叠的反向是 unfoldunfold 接受循环函数 finit 状态。我们的函数有循环控制器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。就像 reduce 中的累加器变量一样,您可以将任何初始状态指定为 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' } ]

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

        更好的解决方案是为您的数据创建一个额外的表示,它的结构更适合这种类型的查询。如果您所做的只是创建f.id -> fMap,则可以将查找时间从线性减少到对数。

        unfold 非常强大,适用于各种各样的问题。我有many other answers 以各种方式依赖它。甚至还有一些处理异步的问题。

        如果您遇到困难,请随时提出后续问题:D

        【讨论】:

          猜你喜欢
          • 2015-06-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-22
          • 2016-06-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多