【问题标题】:SelectMany to flatten a nested structureSelectMany 以展平嵌套结构
【发布时间】:2014-05-24 03:56:34
【问题描述】:

我正在解析一个 XML 结构,我的类如下所示:

class MyXml
{
    //...

    List<Node> Content { get; set; }

    //...
}

class Node
{
    // ...

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

    //...
}

MyXml 代表我正在解析的 XML 文件,其元素都称为&lt;node&gt;。每个节点都有一个类型属性,可以有不同的值。

节点的类型与其深度无关。我可以在任何深度级别拥有任何节点类型。

我可以正确解析结构,所以我得到了一个 MyXml 对象,它的内容是一个节点列表,列表中的任何一个节点都可以有子节点等等(我为此使用了递归)。

我需要做的是展平整个结构,只提取某种类型的节点。

我试过了:

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

但它只采用结构深度为 1 的节点。我想在同一个集合中抓取每个节点,无论深度如何,然后过滤我需要的内容。

【问题讨论】:

  • Properties Nodes ans Type in class Node 应标记为 Public
  • 我编辑了将属性标记为公开的代码。另外,我不认为它是重复的,因为事先不知道节点的数量和类型,因此需要递归方法。

标签: c# linq list flatten


【解决方案1】:

这是一个自然递归的问题。使用递归 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);

回复改编自:https://stackoverflow.com/a/17086572/1480391

【讨论】:

  • 感谢您的回答!尽管如此,由于该集合应该是只读的,我想我会在解析 XML 时创建区分列表而不是在解析后执行另一个递归搜索会更好。我担心我错过了一个明显的 LINQ 方法或语法。
  • 很好的答案。在第一个 sn-p 中,您可以简单地传递 n.Nodes.SelectMany(flattener) 而不是创建额外的包装 lambda。
【解决方案2】:

你应该实现一个方法Node.GetFlattened,它返回节点本身,然后在所有子节点上调用它自己:

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

然后您就可以调用此方法,它会递归地返回所有节点,而不管它们的深度如何。这是深度优先搜索,如果你想要广度优先搜索,你将不得不尝试另一种方法。

【讨论】:

    【解决方案3】:
    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);
        }
    }
    

    首先使用函数展平列表并对其进行查询。

    【讨论】:

      猜你喜欢
      • 2021-03-31
      • 2021-11-05
      • 1970-01-01
      • 2019-01-21
      • 2019-10-31
      • 1970-01-01
      • 2020-10-04
      • 2013-11-17
      • 2017-02-02
      相关资源
      最近更新 更多