3

我有一份现有控股清单,我想合并到另一个控股清单中。我知道使用 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";
}
4

5 回答 5

1

让函数改变原始列表使其非常非 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 上的相等性以比较 Sectors 或创建IEqualityComparer<Holding>

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;
};
于 2012-04-28T01:51:58.513 回答
0

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

如何:从多个源填充对象集合 (LINQ)

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

于 2012-05-04T05:45:21.733 回答
0

你的问题有点模棱两可,你想摆脱 foreach 循环是因为for 循环更快,还是因为你觉得你有太多循环,还是因为你想要更好的性能?

假设这是一个关于提高性能的问题,我建议将现有的Holdings 从List 更改为一个SortedList,其中T 是Holding.Sector 的类型。为了获得最佳性能,Sector 应该是整数变量类型,如 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 值,因为它增加了通过该集合的额外通道。

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

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

于 2012-04-28T01:43:06.170 回答
0

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

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);
    }
}

在性能方面,我不确定这如何成立。但它似乎更优雅一些。

于 2012-04-28T01:10:30.480 回答
0

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

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)
于 2012-04-28T00:36:47.663 回答