我有一个类Product
来保存给定产品的特定实例。
此类包含与主要产品类似的相关产品列表。
class Product
{
public string Name;
public double Rating;
public List<Product> RelatedProducts;
//...
public List<Product> GetTopRelatedProducts(int N)
{
// How to implement this method
// What I did so far(Bad code)
// 1- INFINITE RECURSION
// 2- Can not remember visited objects
var myList = new List<Product>();
foreach(Product prod in RelatedProducts)
{
myList.AddRange(prod.GetTopRelatedProducts(N));
}
return myList.Distinct().OrderByDescending(x => x.Rating).Take(N).ToList();
}
}
我想在Product
课程中定义一个方法,以获得最高N
(评分最高)的相关产品。此方法应考虑RelatedProducts
列表中的元素属于Product
类型,并且它们也有自己的RelatedProducts
列表。因此,我应该继续导航嵌套对象,直到达到所有相关产品,然后取出前N个产品。
我的意思是,解决方案不仅仅是this.RelatedProducts.OrderByDescending(x => x.Rating).Take(N);
还要记住的另一件事:两种产品可以相互关联。这意味着产品 A 可以属于产品的RelatedProducts
列表 B , B 也属于RelatedProducts
产品清单 A 。
有任何建议如何以最佳方式解决此问题?
想象一下,我有数百万种产品需要维护。如何递归浏览所有相关产品并识别已访问过的产品?
我将其标记为C#和Java,因为相同的逻辑可以应用于两种语言
答案 0 :(得分:16)
想象一下,我有数百万种产品需要维护。如何递归浏览所有相关产品并识别已访问过的产品?
它不需要递归。显式Stack
或Queue
可以为导航部分提供服务。要收集结果,可以使用HashSet
代替List
。它有两个目的 - 允许您跳过已访问过的元素,最后也不需要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
是最大深度。
两种迭代实现的好处当然是它们可以处理比导致{{1}}的递归解决方案更大的深度。但是如果预计深度不会那么大而你更喜欢递归,那么这里有几个递归实现(他们只需要访问StackOverflowExpection
和relatedSet
):
使用经典的私有递归方法:
this
使用递归lambda:
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);
}
最后但并非最不重要的是,添加了最新的C#7.0 - 递归local function:
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();
}
所有这些方法最佳地处理(IMO)收集部分。前N部分当然不是最优的 - O(N * log(N))并且可以根据@Amit Kumar的答案进行优化,但它需要实现一个缺失的标准数据结构,超出了SO答案的范围。
答案 1 :(得分:4)
我建议使用固定大小为N的优先级队列(最小堆)。在构建列表时构建优先级队列,因此在初始构建操作之后,优先级队列将具有前N个额定产品。可以通过检查O(log(N))
中优先级队列中的顶部元素来检查后续添加/删除。
伪代码:要添加的新元素E
while PQ.size < N
PQ.enqueue(E)
if PQ.size == N
Etop = PQ.top() < Min heap element >
if E.rating > Etop.rating
PQ.dequeu()
PQ.enqueue(E)
要获得前N个元素,只需遍历PQ。
答案 2 :(得分:2)
我的解决方案:
public List<Product> GetTopRelatedProducts(int N)
{
List<Product> visitedProducts = new List<Product>();
Queue<Product> ProductsQueue = new Queue<Product>();
visitedProducts.add(this);
foreach (product prod in relatedProducts)
if(prod != this) //if a product can't be related to itself then remove this if statement
ProductsQueue.Enqueue(prod);
//for now visitedproducts contains only our main product and ProductsQueue contains the product related to it.
while (ProductsQueue.count > 0)
{
Product p = ProductsQueue.Dequeue();
visitedProducts.add(p);
foreach (product prod in p.relatedProducts)
{
if( ! visitedProduct.contains(prod) && !ProductsQueue.contains(prod))//if we haven't visited the product already or if it is not in the queue so we are going to visit it.
ProductsQueue.Enqueue(prod);
}
}
//now visitedProducts contains all the products that are related (somehow :P) to your first product
visitedProducts.remove(this);// to remove the main product from the results
//all what is left to do is to take the top N products.
return visitedProducts.OrderByDescending(x => x.Rating).Take(N).ToList();
}
我试图让它变得尽可能简单;)
答案 3 :(得分:1)
你只需要LINQ。首先使用您的所有条件获取所有数据,然后最后使用.Take(N)
。你的问题将得到解决。 :)