基於路徑串,分類目錄串,以及資料庫查詢記錄源,分別用js物件導向,函式,及過程的方式構建樹形資料結構
題面
寫一個演算法實現字串陣列按照一定格式轉為樹形結構
const arr = [
'root/lufei.jpg',
'root/font/VarelaRound.ttf',
'root/font/SourceSansPro.ttf',
'root/img/avator.jpg',
'root/img/favicon.jpg',
'root/img/bg5.jpg',
'root/img/bg4.jpg',
'root/img/upload/a.png',
'root/img/nt/mvvm.png',
'root/img/nt/android.png',
'root/img/nt/upload.png',
'root/img/nt/js.png',
'root/img/mvvm/a.png',
];
轉為類似如下樹形
"name": "root",
"children": [
{
"name": "lufei.jpg"
},
{
"name": "font",
"children": [
{
"name": "VarelaRound.ttf"
},
{
"name": "SourceSansPro.ttf"
}
]
},
...
思路
以/
分割,每段示為一個節點,節點有兩個屬性,其中children 是節點型別的陣列,其節點資料結構描述如下
type Node {
name string
children []*Node
}
顯然還需要一個節點查詢方法,子節點遞迴插入
類方案
用js原型+建構函式實現程式碼
function O() {
this.name = 'Trie'
this.children = []
}
// 第一個匹配成功節點,用於插入
O.prototype.matchChild = function (part) {
for (let i = 0; i < this.children.length; i++) {
if (this.children[i].name == part) {
return this.children[i]
}
}
return null
}
/**
* 插入節點
* @param {[]string} parts
* @param {Number} height
*/
O.prototype.insert = function (parts, height) {
if (parts.length == height) {
// 清除子節點
delete this.children
// 必須有退出機制
return
}
part = parts[height]
// 查詢第一個同級同名節點
child = this.matchChild(part)
if (child == null) {
// 新建子節點
child = new O()
child.name = part
this.children.push(child)
}
// 進入下一層繼續搜尋插入
child.insert(parts, height + 1)
}
測試
o = new O()
arr.map(v => {
t = v.split('/')
o.insert(t, 0)
})
console.log(JSON.stringify(o,null,2))
效果
{
"name": "Trie",
"children": [
{
"name": "root",
"children": [
{
"name": "lufei.jpg"
},
{
"name": "font",
"children": [
{
"name": "VarelaRound.ttf"
},
{
"name": "SourceSansPro.ttf"
}
]
},
{
"name": "img",
"children": [
{
"name": "avator.jpg"
},
{
"name": "favicon.jpg"
},
{
"name": "bg5.jpg"
},
{
"name": "bg4.jpg"
},
{
"name": "upload",
"children": [
{
"name": "a.png"
}
]
},
{
"name": "nt",
"children": [
{
"name": "mvvm.png"
},
{
"name": "android.png"
},
{
"name": "upload.png"
},
{
"name": "js.png"
}
]
}
]
}
]
},
{
"name": "abc",
"children": [
{
"name": "img",
"children": [
{
"name": "mvvm",
"children": [
{
"name": "a.png"
}
]
}
]
}
]
}
]
}
函式方案
已知如下陣列
let srcList = ['動物-昆蟲-螞蟻', '動物-昆蟲', '植物-草-綠色', '植物-花-紅色','植物-花-黃色']
達成結構
[
{
"name": "動物",
"children": [
{
"name": "昆蟲",
"children": [
{
"name": "螞蟻"
}
]
}
]
},
{
"name": "植物",
"children": [
{
"name": "草",
"children": [
{
"name": "綠色"
}
]
},
{
"name": "花",
"children": [
{
"name": "紅色"
},
{
"name": "黃色"
}
]
}
]
}
]
解法詳情
function listToTree(srcList) {
// 1.記錄根位置
let destList = []
srcList.forEach(path => {
// 2.待匹配項
let pathList = path.split('-')
// 3.將移動指標重置頂層,確保每次從根檢索匹配(必須!!!)
let levelList = destList
// 4.遍歷待詢節點
for (let name of pathList) {
// 5.同層同名節點查詢匹配
let obj = levelList.find(item => item.name == name)
// 6.若不存在則建立該節點
if (!obj) {
obj = { name, children: [] }
levelList.push(obj)
// 7.若當前被增節點是葉子節點,則裁剪該節點子節點屬性
if (name == pathList[pathList.length - 1]) {
delete obj.children
}
}
// 8.已有則進入下一層,繼續尋找
levelList = obj.children
}
})
return destList
}
測試
let srcList = ['動物-昆蟲-螞蟻', '動物-昆蟲', '植物-草-綠色', '植物-花-紅色','植物-花-黃色']
let result = listToTree(srcList)
console.log(JSON.stringify(result, null, 2))
資料庫變樹形
通常從資料庫查詢的樹形條源如下
arr = [
{ id: 1, pid: 0 },
{ id: 2, pid: 1 },
{ id: 3, pid: 2 },
{ id: 4, pid: 3 },
{ id: 5, pid: 3 },
{ id: 6, pid: 2 },
{ id: 7, pid: 1 }
]
要轉的樹形結構
0
|
1
/ \
2 7
/
3
3->2->1->0
7->1->0
辦法簡單,粗暴將源資料轉為節點陣列,然後填充建樹
// 轉為節點
nda = arr.map(v => {
return { ...v, children: [] }
})
// 建立父子節點鏈路
nda.forEach(v => {
let parent = nda.find(item => item.id == v.pid)
if (parent) parent.children.push(v)
let child = nda.find(item => item.pid == v.id)
if (child) v.children.push(child)
});
列印id
為1的資料樹形結構
let obj = nda.find(item => item.id == 1)
console.log(JSON.stringify(obj, null, 2))
{
"id": 1,
"pid": 0,
"children": [
{
"id": 2,
"pid": 1,
"children": [
{
"id": 3,
"pid": 2,
"children": [
{
"id": 4,
"pid": 3,
"children": []
},
{
"id": 4,
"pid": 3,
"children": []
},
{
"id": 5,
"pid": 3,
"children": []
}
]
},
{
"id": 3,
"pid": 2,
"children": [
{
"id": 4,
"pid": 3,
"children": []
},
{
"id": 4,
"pid": 3,
"children": []
},
{
"id": 5,
"pid": 3,
"children": []
}
]
},
{
"id": 6,
"pid": 2,
"children": []
}
]
},
{
"id": 2,
"pid": 1,
"children": [
{
"id": 3,
"pid": 2,
"children": [
{
"id": 4,
"pid": 3,
"children": []
},
{
"id": 4,
"pid": 3,
"children": []
},
{
"id": 5,
"pid": 3,
"children": []
}
]
},
{
"id": 3,
"pid": 2,
"children": [
{
"id": 4,
"pid": 3,
"children": []
},
{
"id": 4,
"pid": 3,
"children": []
},
{
"id": 5,
"pid": 3,
"children": []
}
]
},
{
"id": 6,
"pid": 2,
"children": []
}
]
},
{
"id": 7,
"pid": 1,
"children": []
}
]
}
小結
最後一種看起來簡單,實際上取巧了,資料庫取的條目通常是確定,所以可先將所有節點構建出來,後兩兩建立當前節點與上下級之關係。事實上,慣用法是遞迴建立。遞迴建立通常要求有出路,終止遞迴條件。而資料記錄,又恰巧只記錄一個節點的情況。
本作品採用《CC 協議》,轉載必須註明作者和本文連結