Ответ 1
Представьте, у меня есть миллионы продуктов для поддержки. Как можно рекурсивно перемещаться по всем связанным продуктам и распознавать уже посещенные?
Это не обязательно быть рекурсивным. Явным Stack
или Queue
может служить навигационная часть. Для сбора результата вместо List
можно использовать HashSet
. Это будет служить двум целям - позволить вам пропустить уже посещенные элементы, а также устранить необходимость Distinct
в конце.
Вот пример реализации Queue
:
public List<Product> GetTopRelatedProducts(int N)
{
var relatedSet = new HashSet<Product>();
var relatedListQueue = new Queue<List<Product>>();
if (RelatedProducts != null && RelatedProducts.Count > 0)
relatedListQueue.Enqueue(RelatedProducts);
while (relatedListQueue.Count > 0)
{
var relatedList = relatedListQueue.Dequeue();
foreach (var product in relatedList)
{
if (product != this && relatedSet.Add(product) && product.RelatedProducts != null && product.RelatedProducts.Count > 0)
relatedListQueue.Enqueue(product.RelatedProducts);
}
}
return relatedSet.OrderByDescending(x => x.Rating).Take(N).ToList();
}
Обновление:. Для полноты здесь приведены другие возможные реализации связанной части сбора:
С явным Stack
:
public List<Product> GetTopRelatedProducts(int N)
{
if (RelatedProducts == null || RelatedProducts.Count == 0)
return new List<Product>();
var relatedSet = new HashSet<Product>();
var pendingStack = new Stack<List<Product>.Enumerator>();
var relatedList = RelatedProducts.GetEnumerator();
while (true)
{
while (relatedList.MoveNext())
{
var product = relatedList.Current;
if (product != this && relatedSet.Add(product) && product.RelatedProducts != null && product.RelatedProducts.Count > 0)
{
pendingStack.Push(relatedList);
relatedList = product.RelatedProducts.GetEnumerator();
}
}
if (pendingStack.Count == 0) break;
relatedList = pendingStack.Pop();
}
return relatedSet.OrderByDescending(x => x.Rating).Take(N).ToList();
}
Хотя это немного более подробно, чем явная реализация на основе Queue
, этот метод имеет меньше требований к пространству - O (высота), где height
- максимальная глубина.
Преимущество обеих итерационных реализаций заключается в том, что они могут обрабатывать гораздо большую глубину, чем рекурсивные решения, которые могут привести к StackOverflowExpection
. Но если глубина не будет настолько большой, и вы предпочтете рекурсию, то вот пара рекурсивных реализаций (все они должны иметь доступ к relatedSet
и this
):
С классическим частным рекурсивным методом:
public List<Product> GetTopRelatedProducts(int N)
{
var relatedSet = new HashSet<Product>();
GetRelatedProducts(this, relatedSet);
return relatedSet.OrderByDescending(x => x.Rating).Take(N).ToList();
}
private void GetRelatedProducts(Product product, HashSet<Product> relatedSet)
{
if (product.RelatedProducts == null) return;
foreach (var item in product.RelatedProducts)
if (item != this && relatedSet.Add(item))
GetRelatedProducts(item, relatedSet);
}
С рекурсивной лямбдой:
public List<Product> GetTopRelatedProductsD(int N)
{
var relatedSet = new HashSet<Product>();
Action<Product> GetRelatedProducts = null;
GetRelatedProducts = product =>
{
if (product.RelatedProducts == null) return;
foreach (var item in product.RelatedProducts)
if (item != this && relatedSet.Add(item))
GetRelatedProducts(item);
};
GetRelatedProducts(this);
return relatedSet.OrderByDescending(x => x.Rating).Take(N).ToList();
}
Последнее, но не менее важное: с последней версией С# 7.0 - рекурсивной локальной функцией:
public List<Product> GetTopRelatedProducts(int N)
{
var relatedSet = new HashSet<Product>();
GetRelatedProducts(this);
return relatedSet.OrderByDescending(x => x.Rating).Take(N).ToList();
void GetRelatedProducts(Product product)
{
if (product.RelatedProducts == null) return;
foreach (var item in product.RelatedProducts)
if (item != this && relatedSet.Add(item))
GetRelatedProducts(item);
}
}
Все эти методы обрабатывают (IMO) оптимально собирающую часть. Верхняя часть N не является оптимальной - O (N * log (N)) и может быть оптимизирована, как указано в ответе @Amit Kumar, но для этого потребуется внедрить отсутствующую стандартную структуру данных, которая выходит за рамки ответа SO.