使用 LINQ 的对象层次结构的深度优先扁平化集合
Posted
技术标签:
【中文标题】使用 LINQ 的对象层次结构的深度优先扁平化集合【英文标题】:Depth-first flattened collection of an object hierarchy using LINQ 【发布时间】:2012-08-27 22:37:44 【问题描述】:我有一个对象层次结构(MasterNode -> ChildNodes),其中主节点和子节点属于同一类型,并且只有两个级别(***和子级)像这样('A' 是 D、E 的父级和F,'B' 是 G 的父级,等等)
A--+
| D
| E
| F
|
B--+
| G
|
C--+
H
I
假设我有一个 MasterNodes 作为父对象(A、B、C)的 IEnumerable,并且给定一个父对象 X,我可以获得其子对象的 IEnumerable X.children
我知道我可以使用 SelectMany 方法或使用
枚举所有叶(子节点)from parent in Masternodes
from child in parent.children
select child
这会给我这个序列:
[D,E,F,G,H,I]
,但这不是我要的。
什么是获取 MasterNodes 集合中对象的深度优先序列的 LINQ 查询?(返回第一个父级,然后是其所有子级,然后是下一个父级,然后是其所有子级等)
预期的结果应该是这样的序列:
[A,D,E,F,B,G,C,H,I]
更新:
我要求纯 .NET 就绪的 LINQ。我知道我可以定义自己的方法来做事,但我想要一些仅基于框架提供的方法的东西。
【问题讨论】:
LINQ 可以帮助编写易于理解的代码,但它不包含 EnumerateDepthFirstTwoLevelDeep 方法。您需要组合几个 LINQ 方法来获得所需的结果。如果存在这样的方法,那么找到它要比一次编写几行代码花费更长的时间,因为 LINQ 设计人员需要提供数百甚至数千种附加方法来匹配您的特定情况以及许多不同的方法。 这就是我所要求的。针对这个特定案例的这两行 :) Heinzi 的回答正是我想要的 【参考方案1】:如果你有下面这样的课程
public class Node
public string Name;
public List<Node> Children = new List<Node>();
你的 linq 会是
Func<IEnumerable<Node>, IEnumerable<Node>> Flatten = null;
Flatten = coll => coll.SelectMany(n=>n.Concat(Flatten(n.Children)));
测试代码:
Node[] roots = new Node[] new Node()Name="A",new Node()Name="B",new Node()Name="C" ;
roots[0].Children.Add(new Node()Name="D");
roots[0].Children.Add(new Node()Name="E");
roots[0].Children.Add(new Node()Name="F");
roots[1].Children.Add(new Node()Name="G");
roots[2].Children.Add(new Node()Name="H");
roots[2].Children.Add(new Node()Name="I");
Func<IEnumerable<Node>, IEnumerable<Node>> Flatten = null;
Flatten = coll => coll.SelectMany(n=>n.Concat(Flatten(n.Children)));
var result = String.Join(",",Flatten(roots).Select(x=>x.Name));
Console.WriteLine(result);
【讨论】:
@DanielHilgarth 我知道递归 Lamba 不是很易读,但它最终只有 2 行代码。 您也可以使用第三方图库,例如 nuget.org/packages/QuickGraph,其中包含深度优先递归。 @L.B:就我个人而言,我会将其设为常规递归方法,因为它更具可读性。【参考方案2】:如果您需要两个以上的层次结构级别,您可以使用以下扩展方法递归地遍历您的对象图:
public static IEnumerable<T> Flat<T>(this IEnumerable<T> l, Func<T, IEnumerable<T>> f) =>
l.SelectMany(i => new T[] i .Concat(f(i).Flat(f)));
它使用一个函数将给定的IEnumerable<T>
展平,该函数将T
映射到描述数据的父-> 子关系的IEnumerable<T>
。
深度优先展平是通过将每个元素与其子树连接起来,然后用SelectMany
连接它们来完成的。
你可以这样使用它:
var flattened = Masternodes.Flat(c => c.children);
【讨论】:
I 我喜欢这个答案,因为它更通用。 如果您认为更好,请考虑将已接受的答案更改为此答案。谢谢!【参考方案3】:由于您只有两个级别,因此以下方法应该有效:
var result = (from parent in masternodes
select new Node[] parent .Concat(parent.children)).SelectMany(i => i);
首先,它创建父级及其子级的枚举:
[A, D, E, F]
[B, G]
[C, H]
然后用SelectMany
将它们展平。
【讨论】:
谢谢,这实际上是我发布问题后不久想到的。是否有 LINQ 方法可以将 Object x 转换为包含该对象的 IEnumerable?还是我必须使用新的 Node[]... 方式? 虽然这不是回答问题的唯一回复,但我之所以选择它是因为它简短、快速、干净并且直接到位 @Saysmaster:创建单项 IEnumerable 并没有什么真正优雅的。另一种选择是Enumerable.Repat(item, 1)
(请参阅this question)或为此目的创建扩展方法(请参阅this question)。【参考方案4】:
说,我们有以下类:
public class MasterNode : ChildNode
public List<ChildNode> ChildNodes;
public class ChildNode
public string Value;
然后
List<MasterNode> list = new List<MasterNode>
new MasterNode
Value="A",
ChildNodes = new List<ChildNode>
new ChildNodeValue = "D",
new ChildNodeValue = "E",
new ChildNodeValue = "F"
,
new MasterNode
Value="B",
ChildNodes = new List<ChildNode>
new ChildNodeValue = "G"
,
new MasterNode
Value="C",
ChildNodes = new List<ChildNode>
new ChildNodeValue = "H",
new ChildNodeValue = "I"
;
foreach (ChildNode c in list.SelectMany(l =>
List<ChildNode> result = l.ChildNodes.ToList();
result.Insert(0, l);
return result;
))
Console.WriteLine(c.Value);
【讨论】:
以上是关于使用 LINQ 的对象层次结构的深度优先扁平化集合的主要内容,如果未能解决你的问题,请参考以下文章
RestKit 0.20:在 JSON 中使用动态扁平化层次结构