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になるため、2 つのリストを不変として扱うバージョンを次に示します。

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;
};
于 2012-04-28T01:51:58.513 に答える
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

ひょっとしたらこれが役に立つかもしれません。MSDN からのリンク。

方法: 複数のソースからオブジェクト コレクションを設定する (LINQ)

別の質問からこのリンクを見つけましたhttps://stackoverflow.com/a/9746336/1278872

于 2012-05-04T05:45:21.733 に答える
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」キーワードを置くことは、その型を拡張することを意味します

パラメータを見ると、おそらく2つを切り替える方が理にかなっているので、メソッドを呼び出すリストに次のように追加されます-

private static void CombineHoldings(this List<Holding> existingHoldings, List<Holding> holdingsToAdd)
于 2012-04-28T00:36:47.663 に答える
0

質問は少しあいまいです。for ループの方が速いため、foreach ループを削除しますか?

これがパフォーマンスの向上に関する質問であると仮定すると、既存のHoldings を List からSortedList (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 は existingHoldings の要素数、m はholdingsToAdd の要素数です。残念なことに、既存のすべてのHoldings 要素の Fund 値を更新する必要があります。これは、そのコレクションを介して追加のパスを追加するためです。

注:existingHoldingsから常にアイテムを追加/削除している場合は、より高速なSortedDictionaryを使用できます(要素へのアクセスにはSortedListの方が高速ですが、追加/削除には時間がかかります)

編集: LINQ はコレクションを更新するためではなく、コレクションを検索するためのものであることに注意することが重要です。そのため、LINQ を使用して、existingHoldings に存在する、または存在しない HoldingsToAdd を見つけてから、既存のHoldings をループしてファンドを設定し、必要に応じて Percentage を設定することができますが、holdingsToAdd と existingHoldings の両方を注文する必要があり、ループすることになります。コレクションごとに 1 回。O(2*m*log n + n) のオーダーになります。コンパイラは 2 つのクエリを 1 つの呼び出しに結合できる場合がありますが、それでも同様のパフォーマンスが得られ、読みやすさは劣ります。

于 2012-04-28T01:43:06.170 に答える