#c# #lambda #ienumerable
#c# #лямбда #ienumerable
Вопрос:
У меня есть дерево узлов, которое я хотел бы перебирать, чтобы найти всех предков до заданной точки (узла) в дереве. Таким образом, я могу вставить / сохранить его обратно в свою базу данных. Пока у меня есть что-то вроде следующего, что пока оказалось очень полезным:
public IEnumerable<INode> Ancestors()
{
var parent = this.Parent;
while (parent != null)
{
yield return parent;
parent = parent.Parent;
}
}
Я думаю, я должен передать функцию или функцию, чтобы остановить / разорвать последовательность.
Какая это была бы лучшая реализация?.Ta
Отредактировано: даны ответы ниже. Я думаю о чем-то более производительном, например:
public IEnumerable<INode> Ancestors(Func<INode, bool> predicate)
{
var parent = this.Parent;
while (parent != null)
{
if (predicate(parent))
{
yield return parent;
}
else
{
yield break;
}
parent = parent.Parent;
}
}
Правильно ли я говорю, что ответ Джона создаст 2 счетчика?
Комментарии:
1. вам нужен диапазон? Например, получить все до id = X? который может повторно выполнить 15 элементов, если идентификатор начинается с 1 и X = 15
2. да, в этом суть!, но это может быть любой идентификатор или любой предикат
Ответ №1:
Как насчет:
var desiredNode = child.Ancestors().FirstOrDefault(node => node.Id == desiredId);
Это дает null
, если правильный узел не может быть найден.
РЕДАКТИРОВАТЬ: Хорошо, если вам нужна полная последовательность снизу вверх, вы можете просто использовать:
var nodes = child.Ancestors().TakeUntil(node => node.Id == desiredId);
где TakeUntil
это метод, подобный TakeWhile
, но который включает в себя конечный узел, который соответствует предикату. Вы можете найти пример реализации в MoreLINQ. Если вы не возражаете против отсутствия проверки аргументов (которую предоставляет MoreLINQ), это очень просто написать:
public static IEnumerable<T> TakeUntil<T>(this IEnumerable<T> source,
Func<T, bool> predicate)
{
foreach (T item in source)
{
yield return item;
if (predicate(item))
{
yield break;
}
}
}
Вы могли бы встроить функциональность в Ancestors()
метод, но он объединяет две обязанности в одну относительно сложную функцию, вместо того, чтобы иметь две простые функции, которые могут быть составлены с другими простыми функциями в очень общем виде.
Комментарии:
1. Мне нужна вся последовательность, снизу вверх и остановка в идентификаторе
2. должен сказать.. Я доволен, что мистер Скит ответил на мой самый первый вопрос в SO. Спасибо и продолжайте в том же духе.
3. На самом деле …, как будут выполняться два запроса. все сразу или это будет повторяться дважды?. Это операция, которую я намерен использовать довольно часто. На самом деле, вероятно, будет находиться в объекте Entity. хотя мне тоже нравится метод расширения. я думаю, что ответ Джилли может быть более производительным, что в данном случае является простым.
4. @ErMasca: он передает потоки — он не собирается копировать все в список или что-то в этом роде. Представьте, что ваш метод Ancestors() поддерживает курсор в текущей позиции. Он выдает значение для takeUntil, которое затем решает, передавать его вызывающей стороне или нет. Похоже, вам не помешало бы прочитать мою серию блогов Edulinq 🙂 msmvps.com/blogs/jon_skeet/archive/tags/Edulinq/default.aspx (Это не совсем LINQ, но это те же идеи …)
Ответ №2:
Попробуйте это:
public IEnumerable<INode> Ancestors(Func<INode, bool> takeUntil)
{
var parent = this.Parent;
while (parent != null)
{
yield return parent;
if (takeUntil(parent))
{
break;
}
parent = parent.Parent;
}
}
...
var ancestors = node.Ancestors(n => n.Id == 123);
Комментарии:
1. Я думаю, что было бы чище отделить поведение «take until» от поведения «find ancestors». Композиция качается 🙂