我有以下层次结构,我需要将其展平并选择所有Id
。我试过使用这样的SelectMany()
.SelectMany(node => node.Children).Select(node => node.Id)
.这将生成 3,5,6 的列表。是否可以使用 Linq 获取完整列表 1,2,3,4,5,6,7?
- 节点 (Id = 1)
- 节点 (Id = 2)
- 节点 (Id = 3)
- 节点 (Id = 4)
- 节点 (Id = 5)
- 节点 (Id = 6)
- 节点 (Id = 7)
您可以使用
以下扩展方法来平展层次结构(请参阅下面答案更新中的替代平展算法):
public static IEnumerable<T> Flatten<T>(
this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector)
{
foreach (var item in source)
{
yield return item;
var children = childrenSelector(item);
if (children == null)
continue;
foreach (var child in children.Flatten(childrenSelector))
yield return child;
}
}
我采用子选择器并递归生成子项。那么投影很简单:
var result = nodes.Flatten(n => n.Children).Select(n => n.Id);
假设您有以下 Node 类:
public class Node
{
public Node(int id, params Node[] children)
{
Id = id;
if (children.Any())
Children = new List<Node>(children);
}
public int Id { get; set; }
public List<Node> Children { get; set; }
}
然后使用示例层次结构:
List<Node> nodes = new List<Node> {
new Node(1),
new Node(2, new Node(3)),
new Node(4, new Node(5),
new Node(6, new Node(7)))
};
输出将是:
1, 2, 3, 4, 5, 6, 7
更新:您可以在不使用递归的情况下平展层次结构(以获得更好的性能):
public static IEnumerable<T> Flatten<T>(
this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector)
{
Queue<T> queue = new Queue<T>();
foreach (var item in source)
queue.Enqueue(item);
while (queue.Any())
{
T item = queue.Dequeue();
yield return item;
var children = childrenSelector(item);
if (children == null)
continue;
foreach (var child in children)
queue.Enqueue(child);
}
}
您可以使用扩展方法平展层次结构:
public static IEnumerable<Node> Flatten(this IEnumerable<Node> source)
{
if(source == null)
throw new ArgumentNullException("source");
return FlattenIterator(source);
}
private static IEnumerable<Node> FlattenIterator(IEnumerable<Node> source)
{
foreach(var node in source)
{
yield return node;
foreach(var child in node.Children.Flatten())
{
yield return child;
}
}
}
然后,您可以像这样选择 ID:
var ids = source.Flatten().Select(n => n.Id);
容易。
Func<IEnumerable<Node>, IEnumerable<int>> flatten = null;
flatten = ns =>
{
return ns.Select(n => n.Id)
.Concat(ns.SelectMany(n => flatten(n.Children)));
};