LINQ selectのBig O等価


10

代わりにLINQ選択を使用すると、ネストされたループのBig O等価に変更があるかどうかを確認しようとしています。

public void myFunc(List<Foo> fooList, List<Bar> barList)
{
    foreach(Foo foo in fooList)
    {
        foreach(Bar bar in barList)
        {
            if(foo.PropA == bar.PropA && bar.PropZ.HasValue)
                foo.PropC = foo.PropB * bar.PropZ;
        }
    }
}

このネストされたループの例は、複雑さのためにO(n ^ 2)であると思います。

ネストされたループを次のようなLINQ選択に置き換えました。

public void myFunc(List<Foo> fooList, List<Bar> barList)
{
    foreach(Foo foo in fooList)
    {
        Bar bar = (from b in barList
                    where foo.PropA == b.PropA
                    select b).FirstOrDefault();

        if(bar.PropZ.HasValue)
            foo.PropC = foo.PropB * bar.PropZ;
    }
}

他に何もない場合、コードは、「これを処理するために特定のものBarを探している」と明示的に述べているため、少し読みやすくなっています。

私の質問はこれです:LINQ selectを使用するとBig Oの複雑さが軽減されますか?

c#  big-o 

3
これらのそれぞれについて、コンパイラによって生成されたILを比較することは興味深いでしょう。
Dan Pichelman、

@DanPichelman-オタクを狙うのをやめて!はい、私はそれを知るのは興味深いことだと思います。それらは同等ですか?

falseと評価するのにほんの少しの量を超えることが予想される場合は、bars をループしてbar.PropZ.HasValue最初にフィルタリングする時間を節約できますか?本当にあなたの質問に答えません、私はあなたのコードをレビューしています。

1
これらの2つのループは同じことfoo.PropA == bar.PropAをしていますbarListか?編集:間違いなく、2番目NullReferenceExceptionはselectが返されたときにをスローするためnullです。
フィリップケンドール

1
私は推測する.FirstOrDefault()あなたのダムネストされたループが早期に終了していない一方で、一致するものが見つかった場合は、早期LINQループ出口を行いますので、はい、私は、LINQは、より良いビッグああを持っているだろうと思います。しかし、定かではないので、コメントではなく回答です。
マイク・ナキス、

回答:


14

両方のコードが(最悪の場合)両方のリストの項目の各組み合わせについて比較を実行する必要があるため、Big Oは同じです。たとえば、リスト間に一致がない場合、Linq実装の最適化では、すべてのアイテムをチェックする必要がなくなります。

しかし、ねえ、あなたは本当にbig-Oについて知りたくないのですか?2番目の速いかどうかを知りたい。答えは「はい」です。linqベースのソリューションは、リスト内の最初の一致が見つかるまでチェックを実行するだけなので、linqベースのソリューションの方が高速です(十分な大きさがある限り)。 。このWhereメソッドはリスト全体をすぐにはスキャンしませんが、評価者は遅延イテレータを作成します。次に、FirstOrDefaultメソッドは最初の一致が見つかるまでイテレータを実行します。つまり、一般的なケースでは、リスト全体を走査する必要はありません。もちろん、Linqソリューションにはある程度のオーバーヘッドがあるため、nが十分に低い場合は、nested-forループの方が高速になります。

自分でソースをチェックアウトできます:https : //github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Enumerable.cs

(ただしbreak、最初のサンプルでを追加した場合、2つのコードは意味的に同等になり、オーバーヘッドが少ないため、最初のコードはより高速になります。)


「linqベースのソリューションの方が速い」誰が知っていますか?LINQには、簡単に2倍を超える可能性がある定数係数のオーバーヘッドがあります。
usr

@usr:その通りです。リストのサイズではなく、一致の頻度に依存します。一致の頻度が高いほど、linqベースのソリューションの相対的な利点は大きくなります。
JacquesB 2015年

5

複雑さに違いはありません。LinqのWhereはO(n)であるため、どちらもO(n²)です。
FirstOrDefaultの使用は、これを行うことと同等です。

if(foo.PropA == bar.PropA && bar.PropZ.HasValue) 
{
    foo.PropC = foo.PropB * bar.PropZ;
    break;
}

これは計算時間の改善ですが、複雑さは改善されません。

2つのリストのいずれかにHashmapを使用することで、より適切に実行できます。LINQのの参加 2つの一致する要素を検索するときにパフォーマンスに得ることができますので、オペレータは、2つのリストのいずれかのHashMapを作成します。

    public void myFunc(List<Foo> fooList, List<Bar> barList)
    {
        var rows = fooList.Join(
                        barList, 
                        f => f.PropA, 
                        b => b.PropA, 
                        (f, b) => new { Foo = f, Bar = b }
                   );

        foreach (var row in rows)
        {
            if (row.Bar.PropZ.HasValue)
                row.Foo.PropC = row.Foo.PropB * row.Bar.PropZ.Value;
        }
    }

これはO(n log(n))で実行する必要があります


O(n log(n))を実行する理由を説明していただけますか? ソースコードに続いて、を作成しLookup(Of TKey, TElement)、反復処理を行いbarListGrouping各項目のを取得し、項目をに追加しますGrouping。次に、を反復処理しfooList、を取得してGrouping、グループ化の各要素を返します。`log(n)はどこから来たのですか?
Zach Mierzejewski 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.