简体   繁体   English

递归.reduce()以输出父级数组

[英]Recursive .reduce() to output an array of parents

I have an flat array of Folders like this one : 我有一个像这样的扁平文件夹:

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

I want to output an array of all parents of a given folder to generate a Breadcrumb-like component of Folder path. 我想输出给定文件夹的所有父项的数组,以生成文件夹路径的类似于面包屑的组件。

I have presently a code that does what I need but I'd like to write it better in a more "functional" way, using reduce recursively. 我目前有一个代码可以满足我的需要,但我想以一种“功能更强”的方式更好地编写它,并递归地使用reduce。

If I do : 如果我做 :

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

and I log the output, I can see it successfully finds the first Parent, then reexecute the code, and outputs the parent's parent... as my initial array is logically reset to [] at each step... Can't find a way around though... 并记录了输出,我可以看到它成功找到了第一个父对象,然后重新执行代码,并输出了父对象的父对象...因为我的初始数组在每一步上都被逻辑重置为[] ...找不到虽然...

You're thinking about it in a backwards way. 您正在反向考虑它。 You have a single folder as input and you wish to expand it to a breadcrumb list of many folders. 您有一个folder作为输入,并且希望将其扩展为包含多个文件夹的痕迹列表。 This is actually the opposite of reduce which takes as input many values, and returns a single value. 这实际上与reduce相反,后者将许多值作为输入并返回一个值。

Reduce is also known as fold , and the reverse of a fold is unfold . 减少也称为折叠折叠的反向展开 unfold accepts a looping function f and an init state. unfold接受循环函数finit状态。 Our function is given loop controllers next which add value to the output and specifies the next state, and done which signals the end of the loop. 给我们的函数提供了next循环控制器,该循环控制器将值添加到输出并指定下一个状态,并donedone ,以信号通知循环结束。

 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 ] 

Above, we start with an initial state of a number, m in this case. 上面,我们从数字的初始状态开始,在这种情况下为m Just like the accumulator variable in reduce, you can specify any initial state to unfold . 就像reduce中的accumulator变量一样,您可以指定要unfold任何初始状态。 Below, we express your program using unfold . 下面,我们使用unfold表示您的程序。 We add parent to make it easy to select a folder's parent 我们添加了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' } ]

You can verify the results of the program below 您可以在下面验证程序的结果

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

If you trace the computation above, you see that find is called once per folder f added to the outupt in the unfolding process. 如果您跟踪上面的计算,您会find在展开过程中,每个添加到outupt的文件夹f都会调用一次find。 This is an expensive operation, and if your data set is significantly large, could be a problem for you. 这是一项昂贵的操作,并且如果您的data集非常大,则可能对您造成问题。

A better solution would be to create an additional representation of your data that has a structure better suited for this type of query. 更好的解决方案是为数据创建附加的表示形式,使其结构更适合此类查询。 If all you do is create a Map of f.id -> f , you can decrease lookup time from linear to logarithmic. 如果您要做的只是创建f.id -> fMapf.id -> f可以将查找时间从线性缩短为对数。

unfold is really powerful and suited for a wide variety of problems. unfold功能非常强大,适用于各种问题。 I have many other answers relying on it in various ways. 我还有许多其他答案 ,它们都以各种方式依赖它。 There's even some dealing with asynchrony in there, too. 甚至还有一些异步处理。

If you get stuck, don't hesitate to ask follow-up questions :D 如果您遇到困难,请随时提出后续问题:D

You could do this with a Map so you avoid iterating over the array each time you need to retrieve the next parent. 您可以使用Map进行此操作,从而避免每次需要检索下一个父对象时都对数组进行迭代。 This way you get an O(n) instead of an O(n²) time complexity: 这样,您将获得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])); 

Just a minor remark: your parentId data type is not consistent: it better be always a string, just like the data type of the id property. 只需稍作说明:您的parentId数据类型不一致:最好始终是字符串,就像id属性的数据类型一样。 If not, you need to cast it in your code, but it is really better to have the data type right from the start. 如果不是这样,则需要将其强制转换为代码,但是最好一开始就拥有数据类型。 You'll notice that I have defined parentId as a string consistently: this is needed for the above code to work. 您会注意到,我已经一致地将parentId定义为一个字符串:上面的代码才能正常工作。 Alternatively, cast it to string in the code with String(folder.parentId) . 或者,使用String(folder.parentId)将其转换为代码中的String(folder.parentId)

Secondly, the above code will pre -pend the parent folder name (like is done in file folder notations). 其次,上面的代码将预先 -pend父文件夹的名称(如在文件夹符号完成)。 If you need to append the parent name after the child, then swap the concat subject and argument: 如果您需要在子项之后添加父项名称,请交换concat主题和参数:

[folder.name].concat(folder.parentId ? getFolderParents(folderMap.get(folder.parentId)) : []);

You can do what you're looking for with a rather ugly looking while loop. 您可以通过一个看起来很丑陋的while循环来完成所需的工作。 Gets the job done though. 虽然完成工作。 Each loop iteration filters, looking for an instance of a parent. 每个循环迭代过滤器,查找父对象的实例。 If that doesn't exist, it stops and exits. 如果不存在,它将停止并退出。 If it does exist, it pushes that parent into the tree array, sets folder to its parent to move up a level, then moves on to the next iteration. 如果确实存在,则将其父级推送到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]) 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM