压缩语法以在C#中创建树结构
本文关键字:创建 结构 语法 压缩 | 更新日期: 2023-09-27 18:21:53
我想以简洁的方式在代码中创建一个有点复杂的树结构。现在我使用这个(简化):
var root = new Tree();
var c1 = root.Add("1");
var c2 = root.Add("2");
var c21 = c2.Add("2-1");
var c22 = c2.Add("2-2");
//...
平均节点宽度为4,树高约为5,因此上述过程非常繁琐,更不用说维护性差了。
让我们假设稍后不会按名称访问这些节点。树/节点类可以根据需要进行调整。性能可以忽略不计。通过XML或类似方式创建是不可能的(节点构造函数在现实中要复杂得多)。
我想要的是以下内容,但我不确定如何在C#中实现。在Java中,这可以通过匿名类实现,而匿名类在C#中不能用于此目的。
var root = new Tree() {
Add("1");
Add("2") {
Add("2-1");
Add("2-2");
};
}
我能想到的最好的解决方案是使用类似于以下的运行声明,我认为这是不易维护的:
// AddXxx returns the added node
var root = new Tree()
.Add("1")
.AddSibling("2")
.Add("2-1")
.AddSibling("2-2")
.AddParent("3")
.Add("3-1")
或者:
// Add now always adds a sibling, Children/Parent steps up/down in the hierarchy
var root = new Tree()
.Children
.Add("1")
.Add("2")
.Children
.Add("2-1")
.Add("2-2")
.Parent
.Add("3")
.Children
.Add("3-1")
我会做这样的事情:
public class Tree
{
public string Name { get; private set; }
public List<Tree> Trees { get; private set; }
public Tree(string name)
{
this.Name = name;
this.Trees = new List<Tree>();
}
public Tree(string name, params Tree[] nodes)
: this(name)
{
if (nodes == null || !nodes.Any()) return;
Trees.AddRange(nodes);
}
}
然后这样使用:
var trees = new List<Tree>
{
new Tree("1"),
new Tree("2",
new Tree("2-1"),
new Tree("2-2",
new Tree("2-2-1"),
new Tree("2-2-1")
)
),
new Tree("3",
new Tree("3-1")
)
};
我受到XElement构造函数的启发,它简化了XML节点的创建,具有更高的可读性。如果您正在使用XML,那么我建议您使用该类。
您可以使用集合初始值设定项:
class Node : IEnumerable<Node> // implement interface to taste
{
public Node(string name)
{
...
}
public void Add(Node n)
{
...
}
}
var root = new Node("x")
{ // Each item in this {} is passed to Add
new Node("y-1")
{
new Node("z-1"),
new Node("z-2")
},
new Node("y-2")
};
实现这一点的传统方法是使用集合初始化器。我不喜欢,因为这意味着你需要实现一个Add
方法,而这意味着你必须实现一个可变的Tree接口,这是(IMHO)的一个麻烦,使许多传统的树算法变得不安全。(您也可以实现freezable TreeBuilder,但这同样麻烦)。
我更喜欢在构建时列出所有的树节点,使用C#6的using static
可以非常干净地完成这项工作。
Linqpad查询:
void Main()
{
Node("test",
Node("kid"),
Node("kid2",
Node("grandchild")
),
Node("kid3",
Node("grandchild2"),
Node("grandchild3")
)
)
.ToString().Dump();
}
public static class Tree
{
public static TreeNode<T> Node<T>(T val, params TreeNode<T>[] kids)
=> new TreeNode<T>(val, kids);
}
public struct TreeNode<T>
{
public readonly T NodeValue;
public readonly IReadOnlyList<TreeNode<T>> Kids;
public TreeNode(T val, TreeNode<T>[] kids)
{
NodeValue = val;
Kids = kids;
}
public override string ToString()
=> $"'n{NodeValue}:{string.Join("", Kids).Replace("'n", "'n ")}";
}
打印:
test:
kid:
kid2:
grandchild:
kid3:
grandchild2:
grandchild3:
请注意,将数组重新解释为IReadOnlyList
并不能保护您免受构造后使params数组发生变异的恶劣调用程序的影响,这在正常项目中可能很好,但对于公共api-YMMV来说可能不那么热门。