小智 42
这是一个可能有帮助的扩展.它将遍历对象层次结构中的所有节点,并选择符合条件的节点.它假定层次结构中的每个对象都有一个包含其子对象的collection属性.
/// 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;
}
Run Code Online (Sandbox Code Playgroud)
首先,我们需要一个对象和一个嵌套的对象层次结构.
一个简单的节点类
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);
}
}
Run Code Online (Sandbox Code Playgroud)
以及获得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;
}
Run Code Online (Sandbox Code Playgroud)
第一次测试:展平层次结构,不进行过滤
[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());
}
Run Code Online (Sandbox Code Playgroud)
这将显示:
Node 1, Level 1
Node 6, Level 1
Node 2, Level 2
Node 3, Level 2
Node 4, Level 3
Node 5, Level 3
Run Code Online (Sandbox Code Playgroud)
第二次测试:获取具有偶数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());
}
Run Code Online (Sandbox Code Playgroud)
这将显示:
Node 6, Level 1
Node 2, Level 2
Node 4, Level 3
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 19
嗯......我不知道究竟你想要的这里,但这里有一个"一平"的选项:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果这不是你想要的,你能提供你想要的签名吗?如果您不需要通用表单,并且您只想做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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,这会将字符串视为字符序列,但是 - 您可能希望将特殊字符串作为单个元素而不是展平它们,具体取决于您的用例.
这有帮助吗?
我以为我会分享一个完整的例子,包括错误处理和单逻辑应用程序.
递归展平很简单:
LINQ版本
public static class IEnumerableExtensions
{
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
return !source.Any() ? source :
source.Concat(
source
.SelectMany(i => selector(i).EmptyIfNull())
.SelectManyRecursive(selector)
);
}
public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> source)
{
return source ?? Enumerable.Empty<T>();
}
}
Run Code Online (Sandbox Code Playgroud)
非LINQ版本
public static class IEnumerableExtensions
{
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
foreach (T item in source)
{
yield return item;
var children = selector(item);
if (children == null)
continue;
foreach (T descendant in children.SelectManyRecursive(selector))
{
yield return descendant;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
设计决策
我决定:
IEnumerable,可以通过删除异常抛出来改变:
source = source.EmptyIfNull();之前添加returnif (source != null)之前添加foreach.EmptyIfNull()在第一个版本中删除- 请注意,SelectMany如果选择器返回null ,则会失败if (children == null) continue;在第二个版本中删除- 请注意,foreach在null IEnumerable参数上将失败.Where在调用者端或子选择器内过滤子句,而不是传递子过滤器选择器参数:
样品使用
我在LightSwitch中使用此扩展方法来获取屏幕上的所有控件:
public static class ScreenObjectExtensions
{
public static IEnumerable<IContentItemProxy> FindControls(this IScreenObject screen)
{
var model = screen.Details.GetModel();
return model.GetChildItems()
.SelectManyRecursive(c => c.GetChildItems())
.OfType<IContentItemDefinition>()
.Select(c => screen.FindControl(c.Name));
}
}
Run Code Online (Sandbox Code Playgroud)
这不是[SelectMany] [1]的用途吗?
enum1.SelectMany(
a => a.SelectMany(
b => b.SelectMany(
c => c.Select(
d => d.Name
)
)
)
);
Run Code Online (Sandbox Code Playgroud)
这是修改后的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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
免责声明:我不知道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=" ")
Run Code Online (Sandbox Code Playgroud)
它打印:
1 2 3 4 5 6 7 8
Run Code Online (Sandbox Code Playgroud)
功能:
public static class MyExtentions
{
public static IEnumerable<T> RecursiveSelector<T>(this IEnumerable<T> nodes, Func<T, IEnumerable<T>> selector)
{
if(nodes.Any() == false)
{
return nodes;
}
var descendants = nodes
.SelectMany(selector)
.RecursiveSelector(selector);
return nodes.Concat(descendants);
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
var ar = new[]
{
new Node
{
Name = "1",
Chilren = new[]
{
new Node
{
Name = "11",
Children = new[]
{
new Node
{
Name = "111",
}
}
}
}
}
};
var flattened = ar.RecursiveSelector(x => x.Children).ToList();
Run Code Online (Sandbox Code Playgroud)