Свести (с номерами уровней) иерархический список

У меня есть коллекция экземпляров класса X, которые указывают на себя (на себя).
Например, мой класс может выглядеть так:

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

Теперь допустим, что у меня есть список экземпляров класса X, которые могут самостоятельно ссылаться на N уровней вниз.

Мой вопрос: как получить экземпляры X с N-го уровня в моем списке?

В основном я пытаюсь сделать в C# то, что рекурсивное самоссылающееся общее табличное выражение будет делать в SQL, то есть выровнять иерархический список, добавив номера уровней.

Я нашел этот пример: /questions/12843426/poisk-v-ierarhicheskom-spiske/12843447#12843447 Это прекрасно работает, но я все еще не могу понять, как получить элементы ТОЛЬКО с N-го уровня.

1 ответ

Решение

Ну, вы можете изменить Flatten метод в связанном примере, чтобы включить номер уровня, а также по следующим направлениям:

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);
        }
    }
}

после этого вы можете просто отфильтровать необходимый уровень:

var thirdLevel = myCollection
           .FlattenLeveled(item => item.Children)
           .Where(leveledItem => leveledItem.Level == 2)
           .Select(leveledItem => leveledItem.Item)

Кроме того, из комментария @Servy, поскольку это первый подход в ширину (все 1-го уровня выполняются до обработки любого 2-го уровня), мы можем использовать Skip/TakeWhile, например:

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);
}

Это будет лениво перечислять, поэтому любой уровень ниже по иерархии не будет обрабатываться вообще.

Другие вопросы по тегам