我有一个名为Category的实体,该实体包含一个名为ChildCategories的IEnumerable。一个类别可以包含这些子类别,它们可以拥有自己的子类别等等。
假设我选择了顶级父类别,我希望获得所有子类别及其子类别等等,以便我拥有该类别的所有层次结构子级。我希望这个奉承并返回初始类别。我尝试过创建像
这样的东西 public static IEnumerable<T> AllChildren<T>(this IEnumerable<T> items,
Func<T, IEnumerable<T>> children, bool includeSelf)
{
foreach (var item in items)
{
if (includeSelf)
{
yield return item;
}
if (children != null)
{
foreach (var a in children(item))
{
yield return a;
children(a).AllChildren(children, false);
}
}
}
}
在使用SelectMany方法之后哪个会变得扁平,但是还没有得到它。
答案 0 :(得分:15)
仅使用LINQ就无法做到这样的事情; LINQ没有任何支持在开箱即用的情况下遍历未知级别的节点。
此外,您没有任何实际的方法来展平结构,所需的属性数量是未知的(因为它与树深度相关,这也是未知的。)
我建议使用iterators in C#来展平树,如下所示:
static IEnumerable<T> Flatten(this IEnumerable<T> source,
Func<T, IEnumerable<T>> childrenSelector)
{
// Do standard error checking here.
// Cycle through all of the items.
foreach (T item in source)
{
// Yield the item.
yield return item;
// Yield all of the children.
foreach (T child in childrenSelector(item).
Flatten(childrenSelector))
{
// Yield the item.
yield return child;
}
}
}
然后,您可以调用扩展方法并将结果放在List<T>
;它和你要得到的一样平坦。
注意,如果层次结构足够深,您可以很容易地抛出StackOverflowException
。为此,您真的想要使用这种非递归方法:
static IEnumerable<T> Flatten(this IEnumerable<T> source,
Func<T, IEnumerable<T>> childSelector)
{
// Do standard error checking here.
// Create a stack for recursion. Push all of the items
// onto the stack.
var stack = new Stack<T>(source);
// While there are items on the stack.
while (stack.Count > 0)
{
// Pop the item.
T item = stack.Pop();
// Yield the item.
yield return item;
// Push all of the children on the stack.
foreach (T child in childSelector(item)) stack.Push(child);
}
}
Stack<T>
实例存在于堆上,而不是存在于调用堆栈中,因此您将不会用尽调用堆栈空间。
此外,如果您需要不同的返回语义(或者您可以通过不同的方式遍历子项),则可以将Stack<T>
更改为Queue<T>
。
如果您需要一个非常具体的订单,我建议您更改方法中的顺序,如果您有大量需要遍历的项目,这会使返回值上的OrderBy
调用禁止。< / p>
答案 1 :(得分:7)
在他的博文Traverse a hierarchical structure with LINQ-to-Hierarchical 中,Arjan Einbu描述了一种扁平化层次结构以便于查询的方法:
我可以制作一个可以展平任何层次结构的通用扩展方法吗? [...]
为此,我们需要分析需要换出方法的哪些部分。这将是TreeNode的Nodes属性。我们可以通过其他方式访问它吗?是的,我认为代表可以帮助我们,所以让我们试一试:
public static IEnumerable<T> FlattenHierarchy<T>(this T node, Func<T, IEnumerable<T>> getChildEnumerator) { yield return node; if(getChildEnumerator(node) != null) { foreach(var child in getChildEnumerator(node)) { foreach(var childOrDescendant in child.FlattenHierarchy(getChildEnumerator)) { yield return childOrDescendant; } } } }
casperOne describes this in his answer as well,以及尝试使用LINQ直接遍历层次结构时固有的问题。
答案 2 :(得分:0)
casperOnes代码存在一些问题。这有效:
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector)
{
// Do standard error checking here.
// Cycle through all of the items.
foreach (T item in source)
{
// Yield the item.
yield return item;
// Yield all of the children.
foreach (T child in childrenSelector(item).Flatten(childrenSelector))
{
// Yield the item.
yield return child;
}
}
}