现在有比 for 循环中的长度更多的节点,因此并非所有项目都会被递归。
我不认为这是真的。您不是在评估element.ChildNodes.Length
一次,而是在每次迭代中进行评估。因此,如果列表是实时的,长度将随着您的更改而变化。
让我们为您的树假设以下简单实现:
class Node
{
readonly List<Node> children;
readonly String name;
public Node(String name)
{
this.children = new List<Node>();
this.name = name;
}
public Node AddChild(Node node)
{
children.Add(node);
return this;
}
public Node InsertChild(int index, Node node)
{
children.Insert(index, node);
return this;
}
public Int32 Length
{
get { return children.Count; }
}
public Node this[Int32 index]
{
get { return children[index]; }
}
public Int32 IndexOf(Node node)
{
return children.IndexOf(node);
}
public Node RemoveChild(Node node)
{
children.Remove(node);
return this;
}
public IEnumerable<Node> Children
{
get { return children.AsEnumerable(); }
}
public override String ToString()
{
var content = new String[1 + children.Count];
content[0] = name;
for (int i = 0; i < children.Count; )
{
var childs = children[i].ToString().Split(new [] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
content[++i] = "+ " + String.Join(Environment.NewLine + " ", childs);
}
return String.Join(Environment.NewLine, content);
}
}
给定的Node
包含子项(但没有父项)和添加、删除、插入、...、子项的简单方法。
让我们看看如何用这种类型构建一个很好的例子Node
:
var root = new Node("Root");
root.AddChild(new Node("a")).
AddChild(new Node("b")).
AddChild(new Node("c").
AddChild(new Node("d").
AddChild(new Node("e")).
AddChild(new Node("f"))).
AddChild(new Node("g")).
AddChild(new Node("h"))).
AddChild(new Node("i"));
调用的输出root.ToString()
将如下所示。
Root
+ a
+ b
+ c
+ d
+ e
+ f
+ g
+ h
+ i
我假设你想把树弄平?正如已经说过的那样,以不可变的方式进行操作可能是一个好主意。有多种方法可以做到这一点,但鉴于上面的 API,我们最终可以得到以下解决方案:
void Flatten(Node element, List<Node> nodes)
{
var before = nodes.Count;
foreach (var node in element.Children)
{
Flatten(node, nodes);
}
if (nodes.Count == before)
{
nodes.Add(element);
}
}
为什么我要传入 a List<Node>
?好吧,我们可以在每个调用中创建一个列表,然后将其与调用者的列表合并,但是,上面的版本更有效一些。此外,我们正在使用该Count
物业来确定是否有人见过任何孩子。我们也可以使用Any()
扩展方法,但这又是一些不必要的开销。我们几乎只是检查给定节点是否是叶子。如果是这样,那么我们将其添加到提供的列表中。
如果您真的想改变原始树,那么您还有其他选择。以下代码采用一个元素,递归遍历其子元素。叶子保持不变,有父母的孩子会将他们的后代附加到父母身上。
void Flatten(Node element, Node parent = null)
{
for (var i = 0; i < element.Length; i++)
{
Flatten(element[i], element);
}
if (parent != null && element.Length > 0)
{
var children = element.Children.ToArray();
var index = parent.IndexOf(element);
parent.RemoveChild(element);
foreach (var child in children)
{
element.RemoveChild(child);
parent.InsertChild(index++, child);
}
}
}
第一次迭代不会改变 的值element.Length
。因此,我们也可以安全地评估它一次,仅此而已。但是,潜在的第二次迭代将做到这一点。这就是为什么我们首先得到一个副本element.Children.ToArray()
。还有另一种没有该副本的方法,它涉及反向 for 循环(从 Length 到 -1)。
让我们看看调用后树的序列化Flatten(root)
会是什么样子。
Root
+ a
+ b
+ e
+ f
+ g
+ h
+ i
希望这个答案对您有所帮助。