SelectMany to flatten a nested structure
我正在分析XML结构,我的类如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class MyXml { //... List<Node> Content { get; set; } //... } class Node { // ... public List<Node> Nodes { get; set; } public string Type { get; set; } //... } |
myxml表示我正在解析的XML文件,其元素都称为
节点的类型未连接到其深度。我可以在任何深度级别使用任何节点类型。
我可以正确解析结构,所以我得到了一个myxml对象,它的内容是一个节点列表,列表中的任何节点都可以有子节点,等等(我使用了递归)。
我需要做的是展平整个结构,只提取特定类型的节点。
我尝试过:
1 | var query = MyXml.Content.SelectMany(n => n.Nodes); |
但它只取结构深度为1的节点。我想在同一个集合中抓取每个节点,不管深度如何,然后过滤我需要的内容。
这是一个自然递归的问题。使用递归lambda,尝试如下操作:
1 2 3 4 5 | Func<Node, IEnumerable<Node>> flattener = null; flattener = n => new[] { n } .Concat(n.Nodes == null ? Enumerable.Empty<Node>() : n.Nodes.SelectMany(flattener)); |
注意,当您这样做递归
还可以使用迭代器块方法扁平列表:
1 2 3 4 5 6 7 8 9 10 | public static IEnumerable<Node> Flatten(Node node) { yield return node; if (node.Nodes != null) { foreach(var child in node.Nodes) foreach(var descendant in Flatten(child)) yield return descendant; } } |
不管怎样,一旦树被展平,就可以对展平的列表执行简单的LINQ查询以查找节点:
1 | flattener(node).Where(n => n.Type == myType); |
响应改编自:https://stackoverflow.com/a/17086572/1480391
您应该实现一个方法
1 2 3 4 5 6 | public IEnumerable<Node> GetFlattened() { yield return this; foreach (var node in this.Nodes.SelectMany(n => n.GetFlattened())) yield return node; } |
然后可以调用这个方法,它递归地返回所有节点,而不管节点的深度如何。这是一个深度优先搜索,如果你想要一个广度优先搜索,你将不得不尝试另一种方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class MyXml { public List<Node> AllNodes() { List<Node> allNodes = new List<Node>(); foreach (var node in Content) AddNode(node, nodes); } public void AddNode(Node node, List<Node> nodes) { nodes.Add(node); foreach (var childNode in node.Nodes) AddNode(childNode, nodes); } public List<Node> AllNodesOfType(NodeType nodeType) { return AllNodes().Where(n => n.NodeType == nodeType); } } |
首先用一个函数展平列表并对其进行查询。