【问题标题】:Is there a better way to do this, maybe replace the for/foreach loop with something LINQ ish?有没有更好的方法来做到这一点,也许用 LINQ ish 替换 for/foreach 循环?
【发布时间】:2012-05-08 17:01:26
【问题描述】:

我有一份现有馆藏清单,我想合并到另一个馆藏清单中。我知道使用 foreach 和 for 循环是一种不好的方法,但我想不出使用 LINQ 来减少这种情况的好方法。

private void CombineHoldings(List<Holding> holdingsToAdd, ref List<Holding> existingHoldings)
{
    foreach (Holding holdingToAdd in holdingsToAdd)
    {
        Boolean found = false;
        for (int i = 0; i < existingHoldings.Count; i++)
        {
            if (existingHoldings[i].Sector == holdingToAdd.Sector)
            {
                found = true;
                existingHoldings[i].Percentage += holdingToAdd.Percentage;
            }
        }
        if (!found)
            existingHoldings.Add(holdingToAdd);
    }
    foreach (Holding holding in existingHoldings)
        holding.Fund = "Combined Funds";
}

【问题讨论】:

  • 如果使用 foreach 和 for 循环是一种不好的方法,那么请避开 Linq,因为 Linq 在幕后使用 foreach 和 for 循环。顺便说一句 - 你应该在 found = true 时在内部 if 循环中添加一个 break,否则,当你已经找到你的项目时,你将继续迭代集合。
  • 你为什么要通过 ref 传递一个列表
  • Merging two IEnumerable<T>s 的可能重复项
  • @Metro Smurf,你太迂腐了。这里没有任何迹象表明 OP 关心性能。因此,我们只能得出结论,OP 认为声明 found 变量非常不令人满意。我想这是我们都可以同情的事情,事实上,LINQ 是一种优秀的工具,可以编写优雅的代码来解决 T 的此类问题。
  • 如果不是迂腐的话,那什么都不是。

标签: c# linq for-loop


【解决方案1】:

让函数改变原始列表使其非常non-Linq,所以这里有一个版本将两个列表视为不可变:

private List<Holding> CombineHoldings(
    List<Holding> holdingsToAdd, 
    List<Holding> existingHoldings) 
{
    var holdings = existingHoldings.Concat(holdingsToAdd)
        .GroupBy(h => h.Sector)
        .Select(g => 
        { 
            var result = g.First(); 
            result.Percentage = g.Select(h => h.Percentage).Sum();
            return result; 
        });
    return holdings.ToList();
}

肯定不会赢得表演比赛,但我喜欢它的简单性。以下可能会更快,但更复杂,并且需要您覆盖 Holdings 上的相等性以比较扇区或创建IEqualityComparer&lt;Holding&gt;

private List<Holding> CombineHoldings(
    List<Holding> holdingsToAdd, 
    List<Holding> existingHoldings) 
{
    var holdings = existingHoldings.GroupJoin(holdingsToAdd, h => h, h => h, 
        (h, toAdd) =>
        new Holding(
            h.Sector, 
            /*Other parameters to clone*/, 
            h.Percentage + toAdd.Select(i => i.Percentage).Sum())
        ).ToList();
    holdings.AddRange(holdingsToAdd.Except(holdings));
    return holdings;
};

【讨论】:

    【解决方案2】:

    如果您经常在列表中调用此方法,那么我建议将其放入列表类型的扩展方法中,即

    private static void CombineHoldings(this List<Holding> holdingsToAdd, ref List<Holding> existingHoldings)
    {
        foreach (Holding holdingToAdd in holdingsToAdd)
        {
            Boolean found = false;
            for (int i = 0; i < existingHoldings.Count; i++)
            {
                if (existingHoldings[i].Sector == holdingToAdd.Sector)
                {
                    found = true;
                    existingHoldings[i].Percentage += holdingToAdd.Percentage;
                }
            }
            if (!found)
                existingHoldings.Add(holdingToAdd);
        }
        foreach (Holding holding in existingHoldings)
            holding.Fund = "Combined Funds";
    }
    

    这将允许您在任何您创建列表的地方去

    List<Holding> temp1 = new List<Holding>();
    List<Holding> temp2 = new List<Holding>();
    //add here to temp1 and temp2
    //then...
    temp1.CombineHoldings(temp2);
    

    将第一个方法设为静态并在第一个参数前面放置一个“this”关键字意味着它将扩展该类型

    查看参数虽然切换两者可能更有意义,因此它会像这样添加到调用方法的列表中 -

    private static void CombineHoldings(this List<Holding> existingHoldings, List<Holding> holdingsToAdd)
    

    【讨论】:

      【解决方案3】:

      我可能会选择这样的东西:

      private void CombineHoldings(List<Holding> holdingsToAdd, ref List<Holding> existingHoldings)
      {
          // group the new holdings by sector
          var groupedHoldings = holdingsToAdd.GroupBy(h => h.Sector);
      
          // now iterate over the groupings
          foreach(var group in groupedHoldings) {
               // calculate the sum of the percentages in the group
               // we'll need this later
               var sum = group.Sum(h => h.Percentage);
      
               // get the index of a matching object in existing holdings
               var existingHoldingIndex = existingHoldings.FindIndex(h => h.Sector == group.Key);
      
               // yay! found one. add the sum of the group and our job's done.
               if(existingHoldingIndex >= 0) {
                   existingHoldings[existingHoldingIndex].Percentage += sum;
                   continue;
               }
      
               // didn't find one, so take the first holding in the group, set its percentage to the sum
               // and append that to the existing holdings table
               var newHolding = group[0];
               newHolding.Percentage = sum;
      
               existingHoldings.Add(newHolding);
          }
      }
      

      就性能而言,我不确定这如何成立。但它似乎更优雅一些。

      【讨论】:

        【解决方案4】:

        你的问题有点模棱两可,你想去掉foreach循环是因为for loops are faster,还是因为你觉得循环太多,还是因为你想要更好的性能?

        假设这是一个关于提高性能的问题,我建议将现有的Holdings 从List 更改为SortedList,其中T 是Holding.Sector 的类型。为获得最佳性能,Sector 应为 integer variable type,如 int。

        private void CombineHoldings(List<Holding> holdingsToAdd, SortedList<int,Holding> existingHoldings) //Remove ref since List and SortedList are reference types and we are not changing the pointer.
        {
        
            for (int i = 0; i < holdingsToAdd.Count; i++)
            {
                if (existingHoldings.ContainsKey(holdingsToAdd[i].Sector))
                {
                    existingHoldings[holdingsToAdd[i].Sector].Percentage += holdingsToAdd[i].Percentage;
                }
                else
                {
                    existingHoldings.Add(holdingsToAdd[i].Sector, holdingsToAdd[i]);
                }
            }
            for (int i = 0; i < existingHoldings.Count; i++)
            {
                existingHoldings.Values[i].Fund = "Combined Funds";
            }
        }
        

        此方法将导致 O(m*log n + n),其中 n 是 existingHoldings 中的元素数,m 是 holdingsToAdd 中的元素数。不幸的是,所有现有的Holdings 元素都必须更新它们的 Fund 值,因为它增加了通过该集合的额外通道。

        注意:如果您经常从现有的Holdings 中添加/删除项目,那么您可以使用SortedDictionary,它应该会更快(SortedList 访问元素更快,但添加/删除需要更长的时间)

        编辑:重要的是要注意 LINQ 用于搜索集合,而不是更新它们。因此,您可以使用 LINQ 查找在 existingHoldings 中存在和不存在的 holdingsToAdd,然后循环设置 Fund 和如果需要设置 Percentage 的现有Holdings,但随后将需要对 holdingsToAdd 和 existingHoldings 进行排序,并且您仍将循环每个收藏一次。这将是 O(2*m*log n + n) 的量级。编译器可能能够将这两个查询组合到一个调用中,但即便如此,您也会看到相似的性能,但可读性较差。

        【讨论】:

          【解决方案5】:

          也许这可能有用。来自 MSDN 的链接。

          How to: Populate Object Collections from Multiple Sources (LINQ)

          从另一个问题https://stackoverflow.com/a/9746336/1278872找到这个链接

          【讨论】:

            猜你喜欢
            • 2012-02-12
            • 2021-06-01
            • 1970-01-01
            • 2022-12-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多