由于nHibernate的支持,我处理的一些数据结构是嵌套列表。例如,我有一个名为“category”的数据对象,它具有.Children属性,该属性解析为一个类别列表...每个类别都可以具有子类别...以此类推。
我需要找到一种方法,从这个结构中的顶级类别开始,获取所有子代的列表、数组或类似的东西,并将其展开为单个列表,包括所有子代及其子代等等。
我相信可以通过递归来完成,但我发现递归代码很难理解和编写,我确信在.Net 4中使用Linq或类似的东西有更简单的方法 - 有什么建议吗?
由于nHibernate的支持,我处理的一些数据结构是嵌套列表。例如,我有一个名为“category”的数据对象,它具有.Children属性,该属性解析为一个类别列表...每个类别都可以具有子类别...以此类推。
我需要找到一种方法,从这个结构中的顶级类别开始,获取所有子代的列表、数组或类似的东西,并将其展开为单个列表,包括所有子代及其子代等等。
我相信可以通过递归来完成,但我发现递归代码很难理解和编写,我确信在.Net 4中使用Linq或类似的东西有更简单的方法 - 有什么建议吗?
这里有一个扩展方法可以完成这个工作:
// 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);
}
}
}
我正在使用 LinkedList<T>
,因为插入操作是 O(1) 的,而插入到 List<T>
中则是 O(n)。
假设您的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;
}
// 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);
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;
}
}
}