递归列表展平
我可能可以自己写这篇文章,但是我试图实现这一目标的特定方式却使我失望。我正在尝试编写一种与.NET 3.5中引入的扩展方法类似的通用扩展方法,该方法将嵌套IEnumerable的IEnumerables(依此类推),并将其展平为一个IEnumerable。有人有想法么?
具体来说,我在扩展方法本身的语法方面遇到麻烦,因此我无法使用展平算法。
解决方案
static class EnumerableExtensions { public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> sequence) { foreach(var child in sequence) foreach(var item in child) yield return item; } }
也许是这样吗?或者,我们是说它可能无限深吗?
基本上,我们需要拥有一个主IENumerable,它在递归函数之外,然后在递归函数中(伪代码)
private void flattenList(IEnumerable<T> list) { foreach (T item in list) { masterList.Add(item); if (item.Count > 0) { this.flattenList(item); } } }
虽然我真的不确定我们嵌套在IEnumerable中的IEnumerable的含义是什么?有多少层嵌套?最终的类型是什么?显然我的代码不正确,但是我希望它能引起思考。
嗯...我不确定我们想要的是什么,但是这里有一个"一级"选项:
public static IEnumerable<TElement> Flatten<TElement,TSequence> (this IEnumerable<TSequence> sequences) where TSequence : IEnumerable<TElement> { foreach (TSequence sequence in sequences) { foreach(TElement element in sequence) { yield return element; } } }
如果这不是我们想要的,我们可以提供我们想要的签名吗?如果我们不需要通用形式,而只想做LINQ to XML构造函数那样的事情,那是相当简单的,尽管递归使用迭代器块效率相对较低。就像是:
static IEnumerable Flatten(params object[] objects) { // Can't easily get varargs behaviour with IEnumerable return Flatten((IEnumerable) objects); } static IEnumerable Flatten(IEnumerable enumerable) { foreach (object element in enumerable) { IEnumerable candidate = element as IEnumerable; if (candidate != null) { foreach (object nested in candidate) { yield return nested; } } else { yield return element; } } }
请注意,这会将字符串视为字符序列,但是,根据用例,我们可能希望将特殊情况下的字符串作为单独的元素,而不是将它们展平。
有帮助吗?
这不是[SelectMany] [1]的目的吗?
enum1.SelectMany( a => a.SelectMany( b => b.SelectMany( c => c.Select( d => d.Name ) ) ) );
这是一个可能有用的扩展。它将遍历对象层次结构中的所有节点,并挑选出符合条件的节点。它假定层次结构中的每个对象都有一个收集属性,该属性保存其子对象。
这是扩展名:
/// Traverses an object hierarchy and return a flattened list of elements /// based on a predicate. /// /// TSource: The type of object in your collection.</typeparam> /// source: The collection of your topmost TSource objects.</param> /// selectorFunction: A predicate for choosing the objects you want. /// getChildrenFunction: A function that fetches the child collection from an object. /// returns: A flattened list of objects which meet the criteria in selectorFunction. public static IEnumerable<TSource> Map<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> selectorFunction, Func<TSource, IEnumerable<TSource>> getChildrenFunction) { // Add what we have to the stack var flattenedList = source.Where(selectorFunction); // Go through the input enumerable looking for children, // and add those if we have them foreach (TSource element in source) { flattenedList = flattenedList.Concat( getChildrenFunction(element).Map(selectorFunction, getChildrenFunction) ); } return flattenedList; }
示例(单元测试):
首先,我们需要一个对象和一个嵌套的对象层次结构。
一个简单的节点类
class Node { public int NodeId { get; set; } public int LevelId { get; set; } public IEnumerable<Node> Children { get; set; } public override string ToString() { return String.Format("Node {0}, Level {1}", this.NodeId, this.LevelId); } }
以及获取节点的3级深层次结构的方法
private IEnumerable<Node> GetNodes() { // Create a 3-level deep hierarchy of nodes Node[] nodes = new Node[] { new Node { NodeId = 1, LevelId = 1, Children = new Node[] { new Node { NodeId = 2, LevelId = 2, Children = new Node[] {} }, new Node { NodeId = 3, LevelId = 2, Children = new Node[] { new Node { NodeId = 4, LevelId = 3, Children = new Node[] {} }, new Node { NodeId = 5, LevelId = 3, Children = new Node[] {} } } } } }, new Node { NodeId = 6, LevelId = 1, Children = new Node[] {} } }; return nodes; }
第一次测试:整理层次结构,不进行过滤
[Test] public void Flatten_Nested_Heirachy() { IEnumerable<Node> nodes = GetNodes(); var flattenedNodes = nodes.Map( p => true, (Node n) => { return n.Children; } ); foreach (Node flatNode in flattenedNodes) { Console.WriteLine(flatNode.ToString()); } // Make sure we only end up with 6 nodes Assert.AreEqual(6, flattenedNodes.Count()); }
这将显示:
Node 1, Level 1 Node 6, Level 1 Node 2, Level 2 Node 3, Level 2 Node 4, Level 3 Node 5, Level 3
第二次测试:获取具有偶数NodeId的节点的列表
[Test] public void Only_Return_Nodes_With_Even_Numbered_Node_IDs() { IEnumerable<Node> nodes = GetNodes(); var flattenedNodes = nodes.Map( p => (p.NodeId % 2) == 0, (Node n) => { return n.Children; } ); foreach (Node flatNode in flattenedNodes) { Console.WriteLine(flatNode.ToString()); } // Make sure we only end up with 3 nodes Assert.AreEqual(3, flattenedNodes.Count()); }
这将显示:
Node 6, Level 1 Node 2, Level 2 Node 4, Level 3
SelectMany扩展方法已经做到了。
Projects each element of a sequence to an IEnumerable<(Of <(T>)>) and flattens the resulting sequences into one sequence.
这是乔恩·斯凯特(Jon Skeet)修改后的答案,不仅仅可以允许"一个级别":
static IEnumerable Flatten(IEnumerable enumerable) { foreach (object element in enumerable) { IEnumerable candidate = element as IEnumerable; if (candidate != null) { foreach (object nested in Flatten(candidate)) { yield return nested; } } else { yield return element; } } }
免责声明:我不了解C#。
在Python中也一样:
#!/usr/bin/env python def flatten(iterable): for item in iterable: if hasattr(item, '__iter__'): for nested in flatten(item): yield nested else: yield item if __name__ == '__main__': for item in flatten([1,[2, 3, [[4], 5]], 6, [[[7]]], [8]]): print(item, end=" ")
它打印:
1 2 3 4 5 6 7 8
由于VB中没有yield,并且LINQ提供了延迟执行和简洁的语法,因此我们也可以使用。
<Extension()> Public Function Flatten(Of T)(ByVal objects As Generic.IEnumerable(Of T), ByVal selector As Func(Of T, Generic.IEnumerable(Of T))) As Generic.IEnumerable(Of T) Return objects.Union(objects.SelectMany(selector).Flatten(selector)) End Function