[英]JavaScript recursive function to put multimap data into a tree object
我有一個通用的樹類: myTree = new Tree(string)、appendChildNode(node)、createChildNode(string)、myTree.print()函數和myTree.name、myTree.children等屬性。
我還有一個包含要分解並放入樹對象中的數據的多圖。 讓我們假設,數據中沒有圓圈。 我的想法是讓遞歸建立子分支。 由於某種原因,遞歸不起作用,我不知道為什么。 forEach 中的返回給了我一個“未定義”
我的多圖:
aaaa -> [b, c, d]
c -> [f]
f -> [g]
r -> [p]
所以我為“aaaa”生成的樹應該是:
aaaa
b c d
f
g
我的主要功能:
myMultiMap ... <multimap defined here>
myTree = new Tree('End2End') // my tree object
// lets just pick the 'aaaa' to start with
myMultiMap.get('aaaa').forEach((entry) => {
newNode = new Tree(entry)
myTree.appendChildNode(recurse(newNode))
})
// show the tree after it was built
myTree.print()
遞歸函數
function recurse (node) {
// if the element is in the multimap, it has one or more children
if(myMultiMap.has(node.name)) {
// iterate through all children of the node in the multimap
myMultiMap.get(node.name).forEach((child) => {
// build a new node from the child
newChildnode = new Tree(child);
// build a subtree recursively, since this child could have children itself
return node.appendChildNode(recurse(newChildnode))
})
// if the node is not in the multimap, thus it has no children, so just return the node
} else {
return node
}
}
信息:我采用了這個樹實現: https ://github.com/beforesemicolon/tutorials-files/blob/master/tree-generic.js
使用簡單的樹實現並使用字符串映射到字符串數組來代替 MultiMap 實現,我們可以相當清楚地看到結構:
const tree = (name, children) => ({name, children}) const mapToTree = (multiMap) => (root) => tree (root, (multiMap .get (root) || []) .map (mapToTree (multiMap))) const myMultiMap = new Map ([ ['aaaa', ['b', 'c', 'd']], ['c', ['f']], ['f', ['g']], ['r', ['p']] ]) console .log (mapToTree (myMultiMap) ('aaaa'))
.as-console-wrapper {max-height: 100% !important; top: 0}
如果您想要一個外部包裝節點End2End
,我們可以再包裝一次對tree
的調用:
const myTree = tree ('End2End', mapToTree (myMultiMap) ('aaaa'))
來自評論:
你的回答很精彩,謝謝。
恐怕它絕非輝煌。 我寫這篇文章的目的是回來並在幾個方向上擴展它,並添加更多的解釋。 不知何故,當我關閉我的機器時,我只是看到了一個帶有簡短解釋的有效答案,並發布了它。 在這里,我將嘗試糾正這一點。
雖然恕我直言,對於沒有進一步代碼注釋的初學者(比如我)來說,這有點太高級了,很容易理解。
它實際上不是很先進,並且使用了如果您不知道已經很容易理解的技術。 讓我從一個看起來更熟悉的版本開始。
首先,樹構建功能真的很簡單。 樹只是一個具有name
和children
屬性的對象。 我們通過將名稱和子項列表傳遞給函數tree
來構造一個。 (理想情況下,這應該稱為node
,但它們經常可以互換使用。)我們構建的樹沒有添加或刪除子節點或更改名稱的功能。 雖然沒有什么可以阻止您改變值,但不鼓勵這樣做。 不可變數據通常更容易推理。
我們可以這樣寫:
const tree = (nodeName, nodeChildren) => {
return {
name: nodeName,
children: nodeChildren
}
}
但是當箭頭函數只包含一個return
語句時,我們可以使用基本語法,刪除花括號和return
語句,以產生
const tree = (nodeName, nodeChildren) => ({
name: nodeName,
children: nodeChildren
})
(請注意,當返回的是 Object 字面量時,出於技術原因,您必須將其括在括號中。)
現在,當對象屬性與范圍內變量具有相同名稱時,有一個對象初始值設定項簡寫可以讓您只使用該名稱,因此如果我們更改參數名稱以匹配我們的name
和children
屬性,我們可以簡化它只是
const tree = (name, children) => ({name, children})
讓我們對 main 函數做同樣的事情。 這是mapToTree
的另一個版本:
const mapToTree = (multiMap, root) => {
const childNames = multiMap .get (root) || []
const children = childNames .map (name => mapToTree (multiMap, name))
return tree (root, children)
}
這與我第一次提出的任何一個之間只有兩個真正的區別。 首先,由於局部變量childNames
和children
只使用一次,我將它們內聯以將其變成如下內容:
const mapToTree = (multiMap, root) => {
return tree (root, (multiMap .get (root) || []) .map (name => mapToTree (multiMap, name)))
}
然后通過刪除花括號和return
關鍵字將其變成一個更簡單的箭頭函數,留下:
const mapToTree = (multiMap, root) =>
tree (root, (multiMap .get (root) || []) .map (name => mapToTree (multiMap, name)))
其次,我對這個函數進行了柯里化,以便 ( multiMap (multiMap, root) => ...
它是(multiMap) => (root) => ...
而不是 (multiMap) => (root) => ...。 柯里化有很多好處,我傾向於對大多數函數這樣做,但在這里它簡化了mapToTree (myMultiMap)
是一個現在接受鍵值並返回樹的函數。 因此它適合通過子名稱數組傳遞給map
,我們可以將name => mapToTree (multiMap, name)
替換為mapToTree (multiMap)
,這會返回初始版本:
const mapToTree = (multiMap) => (root) =>
tree (root, (multiMap .get (root) || []) .map (mapToTree (multiMap)))
事實上,我沒有經歷這些步驟。 我已經這樣做了一段時間,我幾乎按照你看到的格式寫了這些,但我希望它能讓它們更清楚一點。
我也不確定您的解決方案是否易於擴展。 例如,如果我還希望每個節點都有自己的“深度”:“5”鍵,以及所有子節點“兄弟”數量的鍵:“12”。
我不太確定您會在這里尋找什么,但是如果您希望節點跟蹤它們在樹中的深度並擁有一個列出后代數量的屬性,那么我們可以像這樣更改它:
const tree = (name, children, depth, descendentCount) => ({name, depth, descendentCount, children}) const mapToTree = (multiMap, root, depth = 0) => { const childNames = multiMap .get (root) || [] const children = childNames .map (name => mapToTree (multiMap, name, depth + 1)) const descendentCount = children .reduce ( (count, child) => count + child.descendentCount + 1, 0 ) return tree (root, children, depth, descendentCount) } const myMultiMap = new Map ([ ['aaaa', ['b', 'c', 'd']], ['c', ['f']], ['f', ['g']], ['r', ['p']] ]) console .log (mapToTree (myMultiMap, 'aaaa'))
.as-console-wrapper {max-height: 100% !important; top: 0}
這些都不涉及您的樹實現或 MultiMap。 看起來這個簡單的 Map 可以很好地替代您的目的。 但是這棵樹比您鏈接到的樹要簡單得多。 我希望這足以讓您根據需要向那個方向擴展,但如果沒有,Bergi 的回答會顯示您做錯了什么以及如何解決它。
這里的重點只是說明遞歸可能有多簡單。
forEach 中的返回給了我一個“未定義”
是的,您不能從forEach
回調中return
。 但這不是你想要做的。 相反,在執行循環后,您需要return node
:
function recurse(node) {
if (myMultiMap.has(node.name)) {
myMultiMap.get(node.name).forEach((child) => {
const newChildnode = new Tree(child);
node.appendChildNode(recurse(newChildnode))
})
return node;
} else {
return node;
}
}
或者干脆
function recurse(node) {
if (myMultiMap.has(node.name)) {
myMultiMap.get(node.name).forEach((child) => {
const newChildnode = new Tree(child);
node.appendChildNode(recurse(newChildnode))
})
}
return node;
}
或者,根本不返回節點,只寫
function recurse(node) {
if (myMultiMap.has(node.name)) {
myMultiMap.get(node.name).forEach((child) => {
const newChildnode = new Tree(child);
recurse(newChildnode);
node.appendChildNode(newChildnode);
})
}
}
或者更好,而是在recurse
函數中移動節點創建:
function recurse(name) {
const node = new Tree(name);
if (myMultiMap.has(name)) {
myMultiMap.get(name).forEach((child) => {
node.appendChildNode(recurse(child))
})
}
return node;
}
順便說一句,在您的主代碼中,您不需要復制循環。 將其簡化為
const myMultiMap = …; // multimap defined here
const myTree = recurse(new Tree('aaaa'));
myTree.print();
或者對於我的上一個版本,分別
const myMultiMap = …; // multimap defined here
const myTree = recurse('aaaa');
myTree.print();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.