选择相关对象的前N个元素

时间:2017-03-27 14:00:52

标签: java c# algorithm recursion

我有一个类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,因为相同的逻辑可以应用于两种语言

4 个答案:

答案 0 :(得分:16)

  

想象一下,我有数百万种产品需要维护。如何递归浏览所有相关产品并识别已访问过的产品?

它不需要递归。显式StackQueue可以为导航部分提供服务。要收集结果,可以使用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}}的递归解决方案更大的深度。但是如果预计深度不会那么大而你更喜欢递归,那么这里有几个递归实现(他们只需要访问StackOverflowExpectionrelatedSet):

使用经典的私有递归方法:

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)。你的问题将得到解决。 :)