使用多个根节点平展 IEnumerable,然后选择 Id 属性



我有以下层次结构,我需要将其展平并选择所有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)));
};

最新更新