简体   繁体   English

SelectMany以展平嵌套结构

[英]SelectMany to flatten a nested structure

I am parsing an XML structure and my classes look like the following: 我正在解析XML结构,我的类看起来像如下:

class MyXml
{
    //...

    List<Node> Content { get; set; }

    //...
}

class Node
{
    // ...

    public List<Node> Nodes { get; set; }
    public string Type { get; set; }

    //...
}

MyXml represents the XML file I am parsing, whose elements are all called <node> . MyXml表示我正在解析的XML文件,其元素都称为<node> Each node has a type attribute, which can have different values. 每个节点都有一个type属性,可以有不同的值。

The type of the node is not connected to its depth. 节点的类型未与其深度相关联。 I can have any node type at any depth level. 我可以在任何深度级别拥有任何节点类型。

I can parse the structure correctly, so I get a MyXml object whose content is a list of Nodes, where ever node in the List can have subnodes and so on (I used recursion for that). 我可以正确地解析结构,所以我得到一个MyXml对象,其内容是节点列表,其中List中的节点可以有子节点等等(我使用了递归)。

What I need to do is flatten this whole structure and extract only the nodes of a certain type. 我需要做的是展平整个结构并仅提取某种类型的节点。

I tried with: 我尝试过:

var query = MyXml.Content.SelectMany(n => n.Nodes);

but it's taking only the nodes with a structure depth of 1. I would like to grab every node, regardless of depth, in the same collection and then filter what I need. 但它只采用结构深度为1的节点。我想在同一个集合中抓取每个节点,无论深度如何,然后过滤我需要的东西。

This is a naturally recursive problem. 这是一个自然递归的问题。 Using a recursive lambda, try something like: 使用递归lambda,尝试类似于:

Func<Node, IEnumerable<Node>> flattener = null;
flattener = n => new[] { n }
    .Concat(n.Nodes == null 
        ? Enumerable.Empty<Node>()
        : n.Nodes.SelectMany(flattener));

Note that when you make a recursive Func like this, you must declare the Func separately first, and set it to null. 请注意,当您像这样生成递归Func ,必须首先单独声明Func ,并将其设置为null。

You could also flatten the list using an iterator-block method: 您还可以使用迭代器块方法展平列表:

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;
    }
}

Either way, once the tree is flattened you can do simple Linq queries over the flattened list to find nodes: 无论哪种方式,一旦树被展平,您可以在展平列表上执行简单的Linq查询以查找节点:

flattener(node).Where(n => n.Type == myType);

Response adapted from: https://stackoverflow.com/a/17086572/1480391 响应改编自: https//stackoverflow.com/a/17086572/1480391

You should implement a method Node.GetFlattened , which returns the node itself and then calls itself on all subnodes: 您应该实现Node.GetFlattened方法,该方法返回节点本身,然后在所有子节点上调用自身:

public IEnumerable<Node> GetFlattened()
{
    yield return this;
    foreach (var node in this.Nodes.SelectMany(n => n.GetFlattened()))
        yield return node;
}

You would then be able to call this method and it recursively returns all nodes regardless of their depth. 然后,您就可以调用此方法,并以递归方式返回所有节点,而不管其深度如何。 This is a depth-first search, if you want a breadth-first search, you will have to try another approach. 这是深度优先搜索,如果您想要广度优先搜索,则必须尝试其他方法。

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);
    }
}

First flatten the list with a function and query on that. 首先使用函数和查询对列表进行展平。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM