有没有更好的方法来做到这一点,也许用 LINQ ish的东西替换 for/foreach 循环

本文关键字:替换 ish 循环 foreach LINQ for 也许 方法 更好 有没有 这一点 | 更新日期: 2023-09-27 18:32:34

我有一个现有资产列表,我想合并到另一个资产列表中。 我知道使用 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";
}

有没有更好的方法来做到这一点,也许用 LINQ ish的东西替换 for/foreach 循环

让函数改变原始列表会使它非常非 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();
}

绝对不会赢得性能比赛,但我喜欢它的简单性。以下内容可能会更快,但更复杂,并且需要您覆盖持股的相等性以比较行业或创建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;
};

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

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)

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

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

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

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

假设这是一个关于提高性能的问题,我建议将现有控股从列表更改为排序列表,其中T是Holding.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 是现有控股中的元素数,m 是 holdingsToAdd 中的元素数。 不幸的是,所有现有的控股元素都必须更新其基金价值,因为它增加了通过该集合的额外传递。

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

编辑:请务必注意,LINQ 用于搜索集合,而不是更新集合。 因此,您可以使用 LINQ 查找现有持股中存在和不存在的持股,然后循环遍历现有持股设置基金,并在需要时设置百分比,但随后需要对持股 ToAdd 和现有持股进行排序,并且您仍将循环遍历每个集合一次。 它将是O(2*m*log n + n(的数量级。 编译器可能能够将两个查询合并到一个调用中,但即便如此,您也会看到类似的性能,但可读性较低。

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

如何:填充来自多个源的对象集合 (LINQ(

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