Well, you can modify the Flatten
method in the linked example to include a level number as well, along these lines:
public class Leveled<T>
{
public T Item {get; set;}
public int Level {get; set;}
}
public static IEnumerable<Leveled<T>> ToLeveled<T>(this IEnumerable<T> sequence,
int level)
{
return sequence.Select(item => new Leveled<T>{ Item = item, Level = level});
}
public static IEnumerable<Leveled<T>> FlattenLeveled<T>(this IEnumerable<T> sequence,
Func<T, IEnumerable<T>> childFetcher)
{
var itemsToYield = new Queue<Leveled<T>>(sequence.ToLeveled(0));
while (itemsToYield.Count > 0)
{
var leveledItem = itemsToYield.Dequeue();
yield return leveledItem;
var children = childFetcher(leveledItem.Item).ToLeveled(leveledItem.Level + 1);
if (children != null)
{
foreach (var child in children)
itemsToYield.Enqueue(child);
}
}
}
after this, you can just filter out the required level:
var thirdLevel = myCollection
.FlattenLeveled(item => item.Children)
.Where(leveledItem => leveledItem.Level == 2)
.Select(leveledItem => leveledItem.Item)
Also, from @Servy's comment, since this is a breadth first approach (all of 1st level get done before any of the 2nd level is processed), we can use Skip/TakeWhile, like this:
public static IEnumerable<T> GetHierarchyLevel<T>(this IEnumerable<T> sequence, Func<T, IEnumerable<T>> childFetcher, int level)
{
return sequence.FlattenLeveled(childFetcher)
.SkipWhile(li => li.Level < level)
.TakeWhile(li => li.Level == level)
.Select(li => li.Item);
}
This will enumerate lazily, so any level further down the hierarchy will not be processed at all.