LINQ рекурсивная функция? - PullRequest
14 голосов
/ 27 января 2011

Давайте возьмем эту n-уровневую глубокую структуру, например:

public class SomeItem 
{
     public Guid ID { get;set; }
     public string Name { get; set; }
     public bool HasChildren { get;set; }
     public IEnumerable<SomeItem> Children { get; set; }
}

Если я хочу получить конкретный Предмет по идентификатору (где-либо в структуре), есть ли некоторая полезность LINQ, которую я могу легко использоватьполучить его в одном выражении или мне нужно использовать некоторую рекурсивную функцию, как показано ниже:

   private SomeItem GetSomeItem(IEnumerable<SomeItem> items, Guid ID)
    {
        foreach (var item in items)
        {
            if (item.ID == ID)
            {
                return item;
            }
            else if (item.HasChildren)
            {
                return GetSomeItem(item.Children, ID);
            }
        }
        return null;
    }

Ответы [ 5 ]

31 голосов
/ 27 января 2011

LINQ на самом деле не очень хорошо делает рекурсию. Ваше решение кажется подходящим - хотя я не уверен, что HasChildren действительно требуется ... почему бы просто не использовать пустой список для элемента без детей?

Альтернативой является написание метода DescendantsAndSelf, который будет возвращать всех потомков (включая сам элемент), что-то вроде этого;

// Warning: potentially expensive!
public IEnumerable<SomeItem> DescendantsAndSelf()
{
    yield return this;
    foreach (var item in Children.SelectMany(x => x.DescendantsAndSelf()))
    {
        yield return item;
    }
}

Однако, если дерево глубокое, оно оказывается очень неэффективным, потому что каждый элемент должен «пройти» через все итераторы своего происхождения. Уэс Дайер написал в блоге об этом , показывая более эффективную реализацию.

В любом случае, если у вас есть такой метод (хотя он реализован), вы можете просто использовать обычное предложение «где» для поиска элемента (или First / FirstOrDefault и т. Д.).

3 голосов
/ 16 сентября 2015

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

    public static IEnumerable<T> IterateTree<T>(this T root, Func<T, IEnumerable<T>> childrenF)
    {
        var q = new List<T>() { root };
        while (q.Any())
        {
            var c = q[0];
            q.RemoveAt(0);
            q.AddRange(childrenF(c) ?? Enumerable.Empty<T>());
            yield return c;
        }
    }

вызывать так:

            var subtree = root.IterateTree(x => x. Children).ToList();
3 голосов
/ 27 января 2011

надеюсь, это поможет

public static IEnumerable<Control> GetAllChildControls(this Control parent)
{
  foreach (Control child in parent.Controls)
  {
    yield return child;

    if (child.HasChildren)
    {
      foreach (Control grandChild in child.GetAllChildControls())
        yield return grandChild;
    }
  }
}
2 голосов
/ 31 августа 2017

Важно помнить, что вам не нужно делать все с помощью LINQ или по умолчанию использовать рекурсию. Есть интересные варианты при использовании структур данных. Ниже приводится простая функция выравнивания на случай, если кому-то будет интересно.

    public static IEnumerable<SomeItem> Flatten(IEnumerable<SomeItem> items)
    {
        if (items == null || items.Count() == 0) return new List<SomeItem>();

        var result = new List<SomeItem>();
        var q = new Queue<SomeItem>(collection: items);

        while (q.Count > 0)
        {
            var item = q.Dequeue();
            result.Add(item);

            if (item?.Children?.Count() > 0)
                foreach (var child in item.Children)
                    q.Enqueue(child);
        }

        return result;
    }
1 голос
/ 27 января 2011

Хотя существуют методы расширения, которые разрешают рекурсию в LINQ (и, вероятно, выглядят как ваша функция), ни один из них не предоставляется "из коробки".

Примеры этих методов расширения можно найти здесь или здесь .

Я бы сказал, что ваша функция в порядке.

...