24

感谢 nHibernate,我使用的一些数据结构是列表中列表中的列表。因此,例如,我有一个名为“category”的数据对象,它有一个 .Children 属性,该属性解析为一个类别列表……每个类别都可以有孩子……等等。

我需要找到一种方法,从该结构中的顶级类别开始,并获取整个结构中所有子级的列表或数组或类似的东西 - 所以所有子级的所有子级等,都扁平化为一个列表。

我确信它可以通过递归来完成,但我发现递归代码很难完成,而且我相信在.Net 4 中必须有一种更直接的方法,使用 Linq 或类似的东西 - 有什么建议吗?

4

4 回答 4

40

这是一个完成这项工作的扩展方法:

// Depth-first traversal, recursive
public static IEnumerable<T> Flatten<T>(
        this IEnumerable<T> source,
        Func<T, IEnumerable<T>> childrenSelector)
{
    foreach (var item in source)
    {
        yield return item;
        foreach (var child in childrenSelector(item).Flatten(childrenSelector))
        {
            yield return child;
        }
    }
}

你可以像这样使用它:

foreach(var category in categories.Flatten(c => c.Children))
{
    ...
}

上面的解决方案是深度优先遍历,如果你想要广度优先遍历,你可以这样做:

// Breadth-first traversal, non-recursive
public static IEnumerable<T> Flatten2<T>(
        this IEnumerable<T> source,
        Func<T, IEnumerable<T>> childrenSelector)
{
    var queue = new Queue<T>(source);
    while (queue.Count > 0)
    {
        var item = queue.Dequeue();
        yield return item;
        foreach (var child in childrenSelector(item))
        {
            queue.Enqueue(child);
        }
    }
}

它还具有非递归的好处...


更新:实际上,我只是想到了一种使深度优先遍历非递归的方法......这里是:

// Depth-first traversal, non-recursive
public static IEnumerable<T> Flatten3<T>(
        this IEnumerable<T> source,
        Func<T, IEnumerable<T>> childrenSelector)
{
    LinkedList<T> list = new LinkedList<T>(source);
    while (list.Count > 0)
    {
        var item = list.First.Value;
        yield return item;
        list.RemoveFirst();
        var node = list.First;
        foreach (var child in childrenSelector(item))
        {
            if (node != null)
                list.AddBefore(node, child);
            else
                list.AddLast(child);
        }
    }
}

我使用 a 是LinkedList<T>因为插入是 O(1) 操作,而对 a 的插入List<T>是 O(n)。

于 2010-10-11T15:46:35.860 回答
13

假设您的 Category 类看起来像:

 public class Category
 {
   public string Name { get; set; }
   public List<Category> Children { get; set; }
 }

我认为没有一种“简单”的非递归方式可以做到这一点。如果您只是在寻找单个方法调用来处理它,那么“简单”的方法是将递归版本写入单个方法调用。可能有一种迭代方法可以做到这一点,但我猜它实际上非常复杂。这就像在不使用微积分的情况下询问“简单”的方法来找到曲线的切线。

无论如何,这可能会做到:

public static List<Category> Flatten(Category root) {

    var flattened = new List<Category> {root};

    var children = root.Children;

    if(children != null)
    {
        foreach (var child in children)
        {
            flattened.AddRange(Flatten(child));
        }
    }

    return flattened;
}
于 2010-10-11T15:35:36.990 回答
1

如果广度优先遍历是可以的,并且您只想使用一些简短的非递归内联代码(实际上是 3 行),请创建一个使用您的根元素初始化的列表,然后通过一个简单的 for 循环对其进行扩展:

// your data object class looks like:
public class DataObject
{
  public List<DataObject> Children { get; set; }
  ...
}

...

// given are some root elements
IEnumerable<DataObject> rootElements = ...

// initialize the flattened list with the root elements
var result = new List<DataObject>(rootElements);
// extend the flattened list by one simple for-loop, 
// please note that result.Count may increase after every iteration!
for (int index = 0; index < result.Count; index++)
  result.AddRange(result[index].Children);
于 2014-10-16T09:44:59.210 回答
1

鉴于@EZHart 提到的类,您还可以使用辅助方法对其进行扩展,我认为在这种情况下更简单。

public class Category
{
    public string Name { get; set; }

    public List<Category> Children { get; set; }

    public IEnumerable<Category> AllChildren()
    {
        yield return this;
        foreach (var child in Children)
        foreach (var granChild in child.AllChildren())
        {
            yield return granChild;
        }
    }   
}
于 2017-09-14T19:33:38.530 回答