【问题标题】:Javascript: Find all parents for element in treeJavascript:查找树中元素的所有父项
【发布时间】:2017-09-26 11:51:59
【问题描述】:

我有对象树,但我找不到具体对象 ID 的所有父项。想象一下,我需要为 id = 5 的对象的每个父对象添加一些新字段。有人可以帮助通过树进行递归循环

var tree = {
  id: 1,
  children: [
  	{
		id: 3,
		parentId: 1,
		children: [
		  	{
				id: 5,
				parentId: 3,
				children: []
			}
		]
	}
  ]
}

console.log(searchTree (tree, 5));

function searchTree (tree, nodeId){
      for (let i = 0; i < tree.length; i++){
        if (tree[i].id == nodeId) {
            // it's parent
            console.log(tree[i].id);
            tree[i].newField = true;
            if (tree[i].parentId != null) {
              searchTree(tree, tree[i].parentId);
            }
        }
      }
 }

【问题讨论】:

  • 你可能会更好地创建一张 id 地图
  • 请通过删除语法错误(.... 等)、将初始对象分配给变量等来使您的 sn-p 可运行
  • 我已将开始帖子编辑为正确的格式。
  • 您说您必须向“ID 为 5 的节点的每个父节点”添加一个新字段,但在您的示例中,您正在向“ID 为 5 的节点”添加一个新字段...我不明白。您需要找到“节点 5”,然后将字段添加到其父节点?还是对它的孩子们是父母?我怎么知道一个节点是否是父节点(在您的示例中,我看到所有节点都是父节点,因为每个节点都有一个子属性)?

标签: javascript recursion tree


【解决方案1】:

数据构造函数

人们需要停止写这样的数据:

const tree = 
  { id: 1, parentId: null, children:
    [ { id: 3, parentId: 1, children:
      [ { id: 5, parentId: 3, children: [] } ] } ] }

并开始使用数据构造函数

写入数据

// "Node" data constructor
const Node = (id, parentId = null, children = Children ()) =>
  ({ id, parentId, children })

// "Children" data constructor
const Children = (...values) =>
  values

// write compound data
const tree =
  Node (1, null, 
    Children (Node (3, 1,
      Children (Node (5, 3)))))

console.log (tree)
// { id: 1, parentId: null, children: [ { id: 3, parentId: 1, children: [ { id: 5, parentId: 3, children: [] } ] } ] }

这使您可以将自己的想法与是否使用{}[] 甚至x =&gt; ... 来包含您的数据等细节区分开来。我会更进一步,创建一个具有保证tag 字段的统一接口——以便以后可以将其与其他通用数据区分开来

stack-sn-ps 在下面这个程序中处理输出是完美的。 不重要数据在打印出来时的样子 - 重要的是我们人类很容易在我们的中读/写>program,我们的程序很容易读取/写入

当/如果您需要特定格式/形状时,将其强制转换为该形状然后;在此之前,请保持它易于使用

const Node = (id, parentId = null, children = Children ()) =>
  ({ tag: Node, id, parentId, children })

const Children = (...values) =>
  ({ tag: Children, values })

// write compound data
const tree =
  Node (1, null, 
    Children (Node (3, 1,
      Children (Node (5, 3)))))

console.log (tree)
// { ... really ugly output, but who cares !.. }

我们开始搜索

我们可以使用简单的loop 辅助函数编写search——但请注意您没有看到的内容;几乎没有逻辑(使用单个三元表达式);没有像 for/while 这样的命令式构造或像 i++ 这样的手动迭代器递增;不使用像push/unshift这样的mutators或像.forEach这样的有效函数;没有对.length 属性的毫无意义的检查或使用[i] 样式查找的直接索引读取——它只是函数和调用;我们不必担心任何其他噪音

const Node = (id, parentId = null, children = Children ()) =>
  ({ tag: Node, id, parentId, children })

const Children = (...values) =>
  ({ tag: Children, values })

const tree =
  Node (1, null, 
    Children (Node (3, 1,
      Children (Node (5, 3)))))

const search = (id, tree = null) =>
  {
    const loop = (path, node) =>
      node.id === id
        ? [path]
        : node.children.values.reduce ((acc, child) =>
            acc.concat (loop ([...path, node], child)), [])
    return loop ([], tree)
  }

const paths =
  search (5, tree) 

console.log (paths.map (path => path.map (node => node.id)))
// [ 1, 3 ]

所以search 返回一个array 路径,其中每个路径都是一个array 节点——为什么会这样?如果 ID 为 X 的子节点出现在树中的多个 位置,则将返回该子节点的所有路径

const Node = (id, parentId = null, children = Children ()) =>
  ({ tag: Node, id, parentId, children })

const Children = (...values) =>
  ({ tag: Children, values })

const tree =
  Node (0, null, Children (
    Node (1, 0, Children (Node (4, 1))),
    Node (2, 0, Children (Node (4, 2))),
    Node (3, 0, Children (Node (4, 3)))))

const search = (id, tree = null) =>
  {
    const loop = (path, node) =>
      node.id === id
        ? [path]
        : node.children.values.reduce ((acc, child) =>
            acc.concat (loop ([...path, node], child)), [])
    return loop ([], tree)
  }
  
const paths =
  search (4, tree) 

console.log (paths.map (path => path.map (node => node.id)))
// [ [ 0, 1 ],
//   [ 0, 2 ],
//   [ 0, 3 ] ]

你不小心写了 list monad

list monad 编码了模糊计算的概念——即可以返回一个或多个结果的计算的概念。让我们对我们的程序做一个小改动 - 这是有利的,因为List 是通用的,现在可以在我们程序中需要这种计算的其他地方使用

如果你喜欢这个解决方案,你可能会喜欢阅读my other answers that talk about the list monad

const List = (xs = []) =>
  ({
    tag:
      List,
    value:
      xs,
    chain: f =>
      List (xs.reduce ((acc, x) =>
        acc.concat (f (x) .value), []))
  })

const Node = (id, parentId = null, children = Children ()) =>
  ({ tag: Node, id, parentId, children })

const Children = (...values) =>
  List (values)

const search = (id, tree = null) =>
  {
    const loop = (path, node) =>
      node.id === id
        ? List ([path])
        : node.children.chain (child =>
            loop ([...path, node], child))
    return loop ([], tree) .value
  }
  
const tree =
  Node (0, null, Children (
    Node (1, 0, Children (Node (4, 1))),
    Node (2, 0, Children (Node (4, 2))),
    Node (3, 0, Children (Node (4, 3)))))

const paths =
  search (4, tree) 

console.log (paths.map (path => path.map (node => node.id)))
// [ [ 0, 1 ],
//   [ 0, 2 ],
//   [ 0, 3 ] ]

【讨论】:

  • 咦,这是什么?我喜欢它。拧结构文字并给事物命名!我不会称它为type,而是tag。但这只是吹毛求疵。 +100。
  • tagtype 这里更准确;如果有的话,为了避免人们已经对类型产生误解 - 更好的是,我很确定它也是计算机程序的结构和解释(Sussman,Abelson)中使用的名称。下次我在办公桌时会更新它^_^
  • 当节点没有子节点时会中断。
  • @eddy 可运行程序的节点没有子节点; Node(4,1), Node(4,2), ...
  • 我必须添加另一个检查来查看是否有孩子(在这种情况下我返回一个空数组)
【解决方案2】:

最简单的解决方案是将树形结构扁平化,这样您就可以查找 id 并执行一个简单的 while 循环

var tree = {
  id: 1,
  children: [
  	{
		id: 3,
		parentId: 1,
		children: [
		  	{
				id: 5,
				parentId: 3,
				children: []
			}
		]
	}
  ]
}

// We will flatten it down to an object that just holds the id with the object
var lookup = {}
function mapIt (node) {
  lookup[node.id] = node;
  //recursive on all the children
  node.children && node.children.forEach(mapIt);
}
mapIt(tree)

// This takes a node and loops over the lookup hash to get all of the ancestors
function findAncestors (nodeId) {
   var ancestors = []
   var parentId = lookup[nodeId] && lookup[nodeId].parentId
   while(parentId !== undefined) {
     ancestors.unshift(parentId)
     parentId = lookup[parentId] && lookup[parentId].parentId
   }
   return ancestors;
}

// Let us see if it works
console.log("5: ",  findAncestors(5))

【讨论】:

    【解决方案3】:

    这是一个工作递归函数的示例。

    玩一会儿,你应该是金子了

    var tree = {
      id: 1,
      children: [{
        id: 3,
        parentId: 1,
        children: [{
          id: 5,
          parentId: 3,
          children: []
        }]
      }]
    }
    
    function mapit(node, parent = null) {
      node.parent = parent;
      if (node.children.length > 0) {
        for (var i = 0; i < node.children.length; i++) {
          var child = node.children[i];
          mapit(child, node);
        }
      }
    }
    mapit(tree);
    console.log(tree);

    【讨论】:

    • 不是在创建循环引用吗?
    • 是的。这是一个关于如何递归处理整个树并将引用返回给父级的示例。只要不尝试字符串化,应该没有问题。
    • for 循环有效地检查 node.children.length (i
    【解决方案4】:

    递归函数并不难。请记住,如果不满足您的参数,则将新级别传递给函数。

    var tree = [{
      id: 1,
      children: [{
        id: 3,
        parentId: 1,
        children: [{
          id: 5,
          parentId: 3,
          children: [{
            id: 6,
            parentId: 5,
            children: [{
              id: 5,
              parentId: 3,
              children: []
            }]
          }]
        }]
      }]
    }]; //wrap first obj in an array too.
    
    searchTree(tree, 5);
    console.log(tree);
    
    function searchTree(tree, nodeId) {
      for (let i = 0; i < tree.length; i++) {
        if (tree[i].id == nodeId) {
          tree[i]; //id found, now add what you need.
          tree[i].newField = "added";
        }//if child has children of its own, continu digging.
        if (tree[i].children != null && tree[i].children.length > 0) {
          searchTree(tree[i].children, nodeId); //pass the original nodeId and if children are present pass the children array to the function.
    
        }
      }
    }

    【讨论】:

    • 谢谢!它部分有效,但对于 4-5 深度级别,未找到主要父级(没有 parentId)。
    • 现在看看,但是对于大型结构,其他映射解决方案更有效。
    猜你喜欢
    • 1970-01-01
    • 2021-09-20
    • 2013-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-02
    • 1970-01-01
    相关资源
    最近更新 更多