我正在解析XML结构,我的类看起来如下:
class MyXml
{
//...
List<Node> Content { get; set; }
//...
}
class Node
{
// ...
public List<Node> Nodes { get; set; }
public string Type { get; set; }
//...
}
MyXml表示我正在解析的XML文件,其元素都称为<node>
。每个节点都有一个type属性,可以有不同的值。
节点的类型未与其深度相关联。我可以在任何深度级别拥有任何节点类型。
我可以正确地解析结构,所以我得到一个MyXml对象,其内容是节点列表,其中List中的节点可以有子节点等等(我使用了递归)。
我需要做的是展平整个结构并仅提取某种类型的节点。
我尝试过:
var query = MyXml.Content.SelectMany(n => n.Nodes);
但它只采用结构深度为1的节点。我想在同一个集合中抓取每个节点,无论深度如何,然后过滤我需要的节点。
答案 0 :(得分:8)
这是一个自然递归的问题。使用递归lambda,尝试类似:
Func<Node, IEnumerable<Node>> flattener = null;
flattener = n => new[] { n }
.Concat(n.Nodes == null
? Enumerable.Empty<Node>()
: n.Nodes.SelectMany(flattener));
请注意,当您像这样制作递归Func
时,必须先单独声明Func
,然后将其设置为null。
您还可以使用迭代器块方法展平列表:
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查询以查找节点:
flattener(node).Where(n => n.Type == myType);
答案 1 :(得分:1)
您应该实现一个方法Node.GetFlattened
,它返回节点本身,然后在所有子节点上调用自身:
public IEnumerable<Node> GetFlattened()
{
yield return this;
foreach (var node in this.Nodes.SelectMany(n => n.GetFlattened()))
yield return node;
}
然后,您可以调用此方法,并以递归方式返回所有节点,无论其深度如何。这是深度优先搜索,如果您想要广度优先搜索,则必须尝试其他方法。
答案 2 :(得分:0)
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);
}
}
首先使用函数和查询对列表进行展平。