基于递归函数合并嵌套无序列表的JavaScript实现问题
解决思路与代码实现
首先我得先明确你的需求:本质是要提取嵌套列表里所有叶子节点的完整路径(从最顶层父项到叶子的文本用空格拼接),对吧?看你给出的期望结果,确实只有没有子项的节点才会出现在最终数组里。
我给你两种实现方式,一种是不需要构建树的迭代式(更高效),另一种是你提到的递归式(基于树形结构),都能满足需求。
方式一:迭代式(无需构建树,直接遍历处理)
这种方式用一个栈来跟踪当前路径,遍历每一项时动态调整栈,同时判断当前项是否为叶子节点,是就拼接路径加入结果。
步骤拆解
- 预处理每一行:解析出每个项的层级(星号数量)和文本内容;
- 维护路径栈:每处理一个项,先把栈截断到当前层级的父级位置,再把当前文本加入栈;
- 判断叶子节点:如果下一项不存在,或者下一项的层级不大于当前层级,说明当前项没有子项,是叶子,就把栈内文本拼接后加入结果。
完整代码
function parseLine(line) { // 匹配开头的星号,兼容星号后有无空格的情况 const match = line.match(/^(\*+)\s*(.*)$/); if (!match) return null; // 过滤无效行 return { level: match[1].length, text: match[2].trim() }; } function flattenNestedList(lines) { const parsedItems = lines.map(parseLine).filter(Boolean); const result = []; const pathStack = []; for (let i = 0; i < parsedItems.length; i++) { const current = parsedItems[i]; const nextItem = parsedItems[i + 1]; // 调整栈:截断到当前层级的前一层,确保栈长度等于当前层级-1 pathStack.splice(current.level - 1); // 将当前文本加入栈,此时栈长度等于当前层级 pathStack.push(current.text); // 判断是否为叶子节点:没有下一项,或者下一项层级不大于当前层级 const isLeaf = !nextItem || nextItem.level <= current.level; if (isLeaf) { result.push(pathStack.join(' ')); } } return result; } // 测试用例 const inputStr = `* item1 * item2 ** item21 ** item22 * item3 ** item31 ** item32 ***item321 * item4`; const lines = inputStr.split('\n'); console.log(flattenNestedList(lines)); // 输出:["item1", "item2 item21", "item2 item22", "item3 item31", "item3 item32 item321", "item4"]
方式二:递归式(先构建树形结构,再递归收集叶子路径)
如果你更倾向于递归实现,可以先把列表转换成树形结构,再递归遍历树的每个节点,收集叶子节点的完整路径。
步骤拆解
- 解析每一行:和上面一样,得到层级和文本;
- 构建树形结构:用栈来跟踪当前节点的层级,把每个项添加到对应的父节点下;
- 递归收集路径:遍历树的每个节点,如果是叶子节点(没有子节点),就把从根到该节点的路径拼接后加入结果。
完整代码
function parseLine(line) { const match = line.match(/^(\*+)\s*(.*)$/); if (!match) return null; return { level: match[1].length, text: match[2].trim() }; } function buildTree(parsedItems) { const root = { children: [] }; // 栈元素:{ node: 当前节点, level: 当前节点的层级 } const stack = [{ node: root, level: 0 }]; for (const item of parsedItems) { // 找到父节点:弹出栈中层级 >= 当前项层级的节点,直到找到层级更小的父节点 while (stack[stack.length - 1].level >= item.level) { stack.pop(); } const parentNode = stack[stack.length - 1].node; const newNode = { text: item.text, children: [] }; parentNode.children.push(newNode); stack.push({ node: newNode, level: item.level }); } return root; } function collectLeafPaths(node, currentPath = [], result = []) { const updatedPath = [...currentPath, node.text]; // 如果是叶子节点,添加路径到结果 if (node.children.length === 0) { result.push(updatedPath.join(' ')); return; } // 递归遍历子节点 for (const child of node.children) { collectLeafPaths(child, updatedPath, result); } } // 测试用例 const inputStr = `* item1 * item2 ** item21 ** item22 * item3 ** item31 ** item32 ***item321 * item4`; const lines = inputStr.split('\n'); const parsedItems = lines.map(parseLine).filter(Boolean); const tree = buildTree(parsedItems); const result = []; // 从根节点的子节点开始递归收集 tree.children.forEach(child => collectLeafPaths(child, [], result)); console.log(result); // 输出和上面一致
两种方式都能完美得到你想要的结果,迭代式的性能更好一些,递归式的逻辑更直观,适合理解嵌套结构。
内容的提问来源于stack exchange,提问作者Pavel Sedlář
相关产品推荐
相关产品推荐

