这是学习相互递归的绝佳机会。输入可以在any order -
function makeIndex (items, indexer)
{ const append = (r, k, v) =>
r.set(k, (r.get(k) || []).concat([ v ]))
return items.reduce
( (r, i) => append(r, indexer(i), i)
, new Map
)
}
function makeTree (index, root = null)
{ const many = (all = []) =>
all.map(one)
const one = (forum = {}) =>
( { ...forum
, subforums: many(index.get(forum.forumId))
}
)
return many(index.get(root))
}
const input =
[{forumId:1,parentId:null,forumName:"Main",forumDescription:"",forumLocked:false,forumDisplay:true},{forumId:2,parentId:1,forumName:"Announcements",forumDescription:"Announcements & Projects posted here",forumLocked:false,forumDisplay:true},{forumId:3,parentId:1,forumName:"General",forumDescription:"General forum, talk whatever you want here",forumLocked:false,forumDisplay:true},{forumId:4,parentId:3,forumName:"Introduction",forumDescription:"A warming introduction for newcomers here",forumLocked:false,forumDisplay:true}]
const result =
makeTree(makeIndex(input, forum => forum.parentId))
console.log(JSON.stringify(result, null, 2))
[
{
"forumId": 1,
"parentId": null,
"forumName": "Main",
"forumDescription": "",
"forumLocked": false,
"forumDisplay": true,
"subforums": [
{
"forumId": 2,
"parentId": 1,
"forumName": "Announcements",
"forumDescription": "Announcements & Projects posted here",
"forumLocked": false,
"forumDisplay": true,
"subforums": []
},
{
"forumId": 3,
"parentId": 1,
"forumName": "General",
"forumDescription": "General forum, talk whatever you want here",
"forumLocked": false,
"forumDisplay": true,
"subforums": [
{
"forumId": 4,
"parentId": 3,
"forumName": "Introduction",
"forumDescription": "A warming introduction for newcomers here",
"forumLocked": false,
"forumDisplay": true,
"subforums": []
}
]
}
]
}
]
使其模块化
Above makeIndex
是以可以索引数据数组的方式编写的,但是makeTree
做出如下假设...forum
, subforums
, and forum.forumId
。当我们考虑模块中的代码时,我们被迫划出分隔线,因此我们的程序变得杂乱无章。
Below, input
定义于main
所以我们保留所有关于input
here -
// main.js
import { tree } from './tree'
const input =
[{forumId:1,parentId:null,forumName:"Main",forumDescription:"",forumLocked:false,forumDisplay:true},{forumId:2,parentId:1,forumName:"Announcements",forumDescription:"Announcements & Projects posted here",forumLocked:false,forumDisplay:true},{forumId:3,parentId:1,forumName:"General",forumDescription:"General forum, talk whatever you want here",forumLocked:false,forumDisplay:true},{forumId:4,parentId:3,forumName:"Introduction",forumDescription:"A warming introduction for newcomers here",forumLocked:false,forumDisplay:true}]
const result =
tree
( input // <- array of nodes
, forum => forum.parentId // <- foreign key
, (forum, subforums) => // <- node reconstructor function
({ ...forum, subforums: subforums(forum.forumId) }) // <- primary key
)
console.log(JSON.stringify(result, null, 2))
当我做一个tree
,我不想考虑制作一个index
第一的。在我们原来的程序中,我应该如何知道tree
需要一个index
?让我们让tree
模块担心 -
// tree.js
import { index } from './index'
const empty =
{}
function tree (all, indexer, maker, root = null)
{ const cache =
index(all, indexer)
const many = (all = []) =>
all.map(x => one(x))
// zero knowledge of forum object shape
const one = (single) =>
maker(single, next => many(cache.get(next)))
return many(cache.get(root))
}
export { empty, tree } // <-- public interface
我们本来可以写成index
直接在函数中tree
模块,但我们想要的行为并不特定于树。单独写一个index
模块更有意义 -
// index.js
const empty = _ =>
new Map
const update = (r, k, t) =>
r.set(k, t(r.get(k)))
const append = (r, k, v) =>
update(r, k, (all = []) => [...all, v])
const index = (all = [], indexer) =>
all.reduce
( (r, v) => append(r, indexer(v), v) // zero knowledge of v shape
, empty()
)
export { empty, index, append } // <-- public interface
编写模块可以帮助您从有意义的部分思考代码,并提高代码的高度可重用性。