How to flatten tree via LINQ?
所以我有一棵简单的树:
1 2 3 4 5 6 | class MyNode { public MyNode Parent; public IEnumerable<MyNode> Elements; int group = 1; } |
我有一台
你可以这样压平一棵树:
1 2 3 | IEnumerable<MyNode> Flatten(IEnumerable<MyNode> e) { return e.SelectMany(c => Flatten(c.Elements)).Concat(new[] {e}); } |
然后可以使用
要获得一些"风格积分",请将
1 2 3 | public static IEnumerable<MyNode> Flatten(this IEnumerable<MyNode> e) { return e.SelectMany(c => c.Elements.Flatten()).Concat(e); } |
要获得"更好的样式"的一些分数,请将
1 2 3 4 5 6 | public static IEnumerable<T> Flatten<T>( this IEnumerable<T> e, Func<T,IEnumerable<T>> f) { return e.SelectMany(c => f(c).Flatten(f)).Concat(e); } |
这样调用此函数:
1 2 | IEnumerable<MyNode> tree = .... var res = tree.Flatten(node => node.Elements); |
如果您希望按预先顺序而不是按后顺序压平,请在
可接受的答案的问题是,如果树很深,则效率很低。如果这棵树很深,那么它就会把那堆树吹倒。您可以使用显式堆栈来解决问题:
1 2 3 4 5 6 7 8 9 10 11 12 | public static IEnumerable<MyNode> Traverse(this MyNode root) { var stack = new Stack<MyNode>(); stack.Push(root); while(stack.Count > 0) { var current = stack.Pop(); yield return current; foreach(var child in current.Elements) stack.Push(child); } } |
假设树的n个节点的高度为h,分支因子大大小于n,则该方法在堆栈空间中为o(1),在堆栈空间中为o(h),在时间上为o(n)。给出的另一种算法是堆栈中的O(H)、堆栈中的O(1)和时间上的O(NH)。如果分支因子比n小,那么h在o(lg n)和o(n)之间,这说明na?如果h接近n,ve算法可以使用大量的堆栈和大量的时间。
既然我们有了遍历,那么您的查询就很简单了:
1 | root.Traverse().Where(item=>item.group == 1); |
为了完整起见,这里是Dasbinkenlight和EricLippert的答案组合。单元测试和一切。-)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public static IEnumerable<T> Flatten<T>( this IEnumerable<T> items, Func<T, IEnumerable<T>> getChildren) { var stack = new Stack<T>(); foreach(var item in items) stack.Push(item); while(stack.Count > 0) { var current = stack.Pop(); yield return current; var children = getChildren(current); if (children == null) continue; foreach (var child in children) stack.Push(child); } } |
更新:
对筑巢水平(深度)感兴趣的人。关于显式枚举器堆栈实现的一个好处是,在任何时候(尤其是在生成元素时),
1 2 | public static IEnumerable<(T Item, int Level)> ExpandWithLevel<T>( this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector) |
和
1 | yield return (item, stack.Count); |
然后我们可以通过在上面应用简单的
1 2 3 | public static IEnumerable<T> Expand<T>( this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector) => source.ExpandWithLevel(elementSelector).Select(e => e.Item); |
原件:
令人惊讶的是,没有人(甚至Eric)显示递归预阶DFT的"自然"迭代端口,因此这里是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public static IEnumerable<T> Expand<T>( this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector) { var stack = new Stack<IEnumerator<T>>(); var e = source.GetEnumerator(); try { while (true) { while (e.MoveNext()) { var item = e.Current; yield return item; var elements = elementSelector(item); if (elements == null) continue; stack.Push(e); e = elements.GetEnumerator(); } if (stack.Count == 0) break; e.Dispose(); e = stack.Pop(); } } finally { e.Dispose(); while (stack.Count != 0) stack.Pop().Dispose(); } } |
如果其他人发现了这一点,但也需要知道他们压平树后的水平,这就扩展到了Konamiman的Dasbinkenlight和Eric Lippert的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public static IEnumerable<Tuple<T, int>> FlattenWithLevel<T>( this IEnumerable<T> items, Func<T, IEnumerable<T>> getChilds) { var stack = new Stack<Tuple<T, int>>(); foreach (var item in items) stack.Push(new Tuple<T, int>(item, 1)); while (stack.Count > 0) { var current = stack.Pop(); yield return current; foreach (var child in getChilds(current.Item1)) stack.Push(new Tuple<T, int>(child, current.Item2 + 1)); } } |
我发现这里给出的答案有一些小问题:
- 如果项目的初始列表为空怎么办?
- 如果子列表中有空值怎么办?
基于先前的答案,得出以下结论:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public static class IEnumerableExtensions { public static IEnumerable<T> Flatten<T>( this IEnumerable<T> items, Func<T, IEnumerable<T>> getChildren) { if (items == null) yield break; var stack = new Stack<T>(items); while (stack.Count > 0) { var current = stack.Pop(); yield return current; if (current == null) continue; var children = getChildren(current); if (children == null) continue; foreach (var child in children) stack.Push(child); } } } |
单元测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | [TestClass] public class IEnumerableExtensionsTests { [TestMethod] public void NullList() { IEnumerable<Test> items = null; var flattened = items.Flatten(i => i.Children); Assert.AreEqual(0, flattened.Count()); } [TestMethod] public void EmptyList() { var items = new Test[0]; var flattened = items.Flatten(i => i.Children); Assert.AreEqual(0, flattened.Count()); } [TestMethod] public void OneItem() { var items = new[] { new Test() }; var flattened = items.Flatten(i => i.Children); Assert.AreEqual(1, flattened.Count()); } [TestMethod] public void OneItemWithChild() { var items = new[] { new Test { Id = 1, Children = new[] { new Test { Id = 2 } } } }; var flattened = items.Flatten(i => i.Children); Assert.AreEqual(2, flattened.Count()); Assert.IsTrue(flattened.Any(i => i.Id == 1)); Assert.IsTrue(flattened.Any(i => i.Id == 2)); } [TestMethod] public void OneItemWithNullChild() { var items = new[] { new Test { Id = 1, Children = new Test[] { null } } }; var flattened = items.Flatten(i => i.Children); Assert.AreEqual(2, flattened.Count()); Assert.IsTrue(flattened.Any(i => i.Id == 1)); Assert.IsTrue(flattened.Any(i => i == null)); } class Test { public int Id { get; set; } public IEnumerable<Test> Children { get; set; } } } |
下面是IvanStoev的代码,其附加功能是告诉路径中每个对象的索引。例如,搜索"item_120":
16将返回项和int数组[1,2,0]。显然,嵌套级别也可用,作为数组的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public static IEnumerable<(T, int[])> Expand<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> getChildren) { var stack = new Stack<IEnumerator<T>>(); var e = source.GetEnumerator(); List<int> indexes = new List<int>() { -1 }; try { while (true) { while (e.MoveNext()) { var item = e.Current; indexes[stack.Count]++; yield return (item, indexes.Take(stack.Count + 1).ToArray()); var elements = getChildren(item); if (elements == null) continue; stack.Push(e); e = elements.GetEnumerator(); if (indexes.Count == stack.Count) indexes.Add(-1); } if (stack.Count == 0) break; e.Dispose(); indexes[stack.Count] = -1; e = stack.Pop(); } } finally { e.Dispose(); while (stack.Count != 0) stack.Pop().Dispose(); } } |
基于Konamiman的回答,以及订购意外的评论,这里有一个带有显式排序参数的版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public static IEnumerable<T> TraverseAndFlatten<T, V>(this IEnumerable<T> items, Func<T, IEnumerable<T>> nested, Func<T, V> orderBy) { var stack = new Stack<T>(); foreach (var item in items.OrderBy(orderBy)) stack.Push(item); while (stack.Count > 0) { var current = stack.Pop(); yield return current; var children = nested(current).OrderBy(orderBy); if (children == null) continue; foreach (var child in children) stack.Push(child); } } |
示例用法:
1 | var flattened = doc.TraverseAndFlatten(x => x.DependentDocuments, y => y.Document.DocDated).ToList(); |
结合戴夫和伊万斯托夫的答案,以防你需要的层次嵌套和名单扁平"有序",而不是像在回答科纳米曼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public static class HierarchicalEnumerableUtils { private static IEnumerable<Tuple<T, int>> ToLeveled<T>(this IEnumerable<T> source, int level) { if (source == null) { return null; } else { return source.Select(item => new Tuple<T, int>(item, level)); } } public static IEnumerable<Tuple<T, int>> FlattenWithLevel<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector) { var stack = new Stack<IEnumerator<Tuple<T, int>>>(); var leveledSource = source.ToLeveled(0); var e = leveledSource.GetEnumerator(); try { while (true) { while (e.MoveNext()) { var item = e.Current; yield return item; var elements = elementSelector(item.Item1).ToLeveled(item.Item2 + 1); if (elements == null) continue; stack.Push(e); e = elements.GetEnumerator(); } if (stack.Count == 0) break; e.Dispose(); e = stack.Pop(); } } finally { e.Dispose(); while (stack.Count != 0) stack.Pop().Dispose(); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | void Main() { var allNodes = GetTreeNodes().Flatten(x => x.Elements); allNodes.Dump(); } public static class ExtensionMethods { public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector = null) { if (source == null) { return new List<T>(); } var list = source; if (childrenSelector != null) { foreach (var item in source) { list = list.Concat(childrenSelector(item).Flatten(childrenSelector)); } } return list; } } IEnumerable<MyNode> GetTreeNodes() { return new[] { new MyNode { Elements = new[] { new MyNode() }}, new MyNode { Elements = new[] { new MyNode(), new MyNode(), new MyNode() }} }; } class MyNode { public MyNode Parent; public IEnumerable<MyNode> Elements; int group = 1; } |