2つの一般的なリストの違いを比較する最も簡単な方法


213

2つの大量の(50.000を超えるアイテム)を比較するための最も迅速な(そしてリソースの消費が最も少ない)ものは何ですか。その結果、以下のような2つのリストがあります。

  1. 最初のリストには表示されるが2番目のリストには表示されないアイテム
  2. 2番目のリストには表示されるが、最初のリストには表示されないアイテム

現在、ListまたはIReadOnlyCollectionを使用しており、この問題をlinqクエリで解決しています。

var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();

しかし、これは私が望むほどうまく機能しません。たくさんのリストを処理する必要があるので、これをより速く、より少ないリソースで行うというアイデアはありますか?

回答:


452

使用Except

var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();

実際にはこれよりわずかに速いアプローチがあると思いますが、これでもO(N * M)のアプローチよりもはるかに高速です。

これらを組み合わせる場合は、上記のメソッドを作成してからreturnステートメントを作成します。

return !firstNotSecond.Any() && !secondNotFirst.Any();

注意すべき1つの点は、問題の元のコードとここでの解決策の結果に違いがあるということです。1つのリストにのみある重複要素は、私のコードで1回だけ報告されますが、それらは多く報告されます元のコードで発生した回数。

たとえば、[1, 2, 2, 2, 3]およびのリストで[1]は、「list1の要素でlist2の要素ではない」という結果の元のコードはになります[2, 2, 2, 3]。私のコードではそれだけです[2, 3]。多くの場合、それは問題にはなりませんが、注意する価値があります。


8
これは本当に大きなパフォーマンスの向上です!この回答をありがとう。
フランク

2
2つの巨大なリストが必要なのですが、比較する前に並べ替えると便利ですか?または拡張メソッドを除いて、渡されたリストは既にソートされています。
Larry

9
@ラリー:ソートされていません。ハッシュセットを作成します。
Jon Skeet、2012年

2
@PranavSingh:それは適切な同等性を持つすべてのもので機能します-したがって、カスタムタイプがオーバーライドEquals(object)または実装IEquatable<T>する場合は、それでも問題ありません。
Jon Skeet

2
@ k2ibegin:IEquatable<T>実装またはobject.Equals(object)メソッドを使用するデフォルトの等値比較子を使用します。最小限の再現可能な例で新しい質問を作成する必要があるようです-コメントで実際に診断することはできません。
Jon Skeet 2017

40

より効率的Enumerable.Exceptです:

var inListButNotInList2 = list.Except(list2);
var inList2ButNotInList = list2.Except(list);

このメソッドは、遅延実行を使用して実装されます。つまり、たとえば次のように書くことができます。

var first10 = inListButNotInList2.Take(10);

内部でa Set<T>を使用してオブジェクトを比較するため、効率的でもあります。最初に2番目のシーケンスからすべての個別の値を収集し、次に最初のシーケンスの結果をストリーミングして、それらが以前に表示されていないことを確認することで機能します。


1
うーん。かなり延期されていません。部分的に延期したと思います。コンプリートSet<T>は2番目のシーケンスから構築され(つまり、完全に反復されて保存されます)、最初のシーケンスから追加できるアイテムが生成されます。
支出

2
@spender、Whereこれはlist.Where(x => x.Id == 5)、数値の値が5遅延して実行されるのではなく、最初に格納されるため、実行が部分的に遅延されることを意味します。
jwg 2014年

27

Enumerable.SequenceEqualメソッド

等値比較子に従って2つのシーケンスが等しいかどうかを判断します。 MS.Docs

Enumerable.SequenceEqual(list1, list2);

これはすべてのプリミティブデータ型で機能します。あなたが実装する必要があるカスタムオブジェクトでそれを使用する必要がある場合IEqualityComparer

等しいかどうかのオブジェクトの比較をサポートするメソッドを定義します。

IEqualityComparerインターフェイス

等しいかどうかのオブジェクトの比較をサポートするメソッドを定義します。 IEqualityComparerのMS.Docs


これは受け入れられる答えになるはずです。問題は、SETSについてではなく、要素の重複を含む可能性があるLISTSについてです。
エイドリアンNasui

3
の結果がSequenceEqual単純であることを考えると、これがどのように答えになるかはわかりませんbool。OPは結果の2つのリストを必要としています-そして、セット操作の観点から彼らが何を望んでいるかを説明します:「最初のリストには表示されるが、2番目のリストには表示されないアイテム」。順序付けが関連していることを示すものはありませんが、SequenceEqual それが関連ていると見なします。これはまったく異なる質問に答えているようです。
Jon Skeet、

はい、正解です。私はこれに速く答えすぎて、リクエストの2番目の部分を見ていませんでした...最初の2つのコメントと同じです...
miguelmpn

9

結果の大文字と小文字を区別しない場合は、以下が機能します。

List<string> list1 = new List<string> { "a.dll", "b1.dll" };
List<string> list2 = new List<string> { "A.dll", "b2.dll" };

var firstNotSecond = list1.Except(list2, StringComparer.OrdinalIgnoreCase).ToList();
var secondNotFirst = list2.Except(list1, StringComparer.OrdinalIgnoreCase).ToList();

firstNotSecondb1.dllが含まれます

secondNotFirstb2.dllが含まれます


5

この問題ではありませんが、リストを比較して等しいかどうかを比較するコードを次に示します。同一のオブジェクト:

public class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> where    T : IEquatable<T>

/// <summary>
/// True, if this contains element with equal property-values
/// </summary>
/// <param name="element">element of Type T</param>
/// <returns>True, if this contains element</returns>
public new Boolean Contains(T element)
{
    return this.Any(t => t.Equals(element));
}

/// <summary>
/// True, if list is equal to this
/// </summary>
/// <param name="list">list</param>
/// <returns>True, if instance equals list</returns>
public Boolean Equals(EquatableList<T> list)
{
    if (list == null) return false;
    return this.All(list.Contains) && list.All(this.Contains);
}

1
これは、カスタムデータ型を比較す​​るために必要なものです。その後、使用Except
Pranav Singh

おそらく、並べ替え可能な型を使用するほうがよいでしょう。これはO(n ^ 2)で実行されますが、O(nlogn)を実行できます。
yuvalm2

3

この方法を試してください:

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));

13
これはひどいパフォーマンスに悩まされ、最初のアイテムごとに2番目のリストをスキャンする必要があります。機能するので反対投票はしませんが、元のコードと同じくらい悪いです。
支出額

3
using System.Collections.Generic;
using System.Linq;

namespace YourProject.Extensions
{
    public static class ListExtensions
    {
        public static bool SetwiseEquivalentTo<T>(this List<T> list, List<T> other)
            where T: IEquatable<T>
        {
            if (list.Except(other).Any())
                return false;
            if (other.Except(list).Any())
                return false;
            return true;
        }
    }
}

2つのリストが異なるかどうかを知るだけでよくそれらの違いを知る必要がない場合もあります。その場合は、この拡張メソッドをプロジェクトに追加することを検討してください。リストされたオブジェクトはIEquatableを実装する必要があります!

使用法:

public sealed class Car : IEquatable<Car>
{
    public Price Price { get; }
    public List<Component> Components { get; }

    ...
    public override bool Equals(object obj)
        => obj is Car other && Equals(other);

    public bool Equals(Car other)
        => Price == other.Price
            && Components.SetwiseEquivalentTo(other.Components);

    public override int GetHashCode()
        => Components.Aggregate(
            Price.GetHashCode(),
            (code, next) => code ^ next.GetHashCode()); // Bitwise XOR
}

Componentクラスが何であれ、ここに示すメソッドはCarほぼ同じように実装する必要があります。

GetHashCodeをどのように記述したかに注目することは非常に重要です。適切に実装するためにIEquatableEqualsGetHashCode しなければならない論理的に互換性のある方法で、インスタンスのプロパティを操作します。

同じ内容の2つのリストはまだ異なるオブジェクトであり、異なるハッシュコードを生成します。これら2つのリストを同等に扱う必要があるGetHashCodeため、それぞれに同じ値を生成させる必要があります。これは、ハッシュコードをリスト内のすべての要素に委任し、標準のビットごとのXORを使用してそれらすべてを結合することで実現できます。XORは順序にとらわれないため、リストのソート順が異なっていてもかまいません。同等のメンバーのみが含まれていることが重要です。

注:奇妙な名前は、メソッドがリスト内の要素の順序を考慮しないことを意味します。リスト内の要素の順序を気にする場合は、この方法は適していません。


1

このコードを使用して、何百万ものレコードがある2つのリストを比較しました。

この方法はそれほど時間はかかりません

    //Method to compare two list of string
    private List<string> Contains(List<string> list1, List<string> list2)
    {
        List<string> result = new List<string>();

        result.AddRange(list1.Except(list2, StringComparer.OrdinalIgnoreCase));
        result.AddRange(list2.Except(list1, StringComparer.OrdinalIgnoreCase));

        return result;
    }

0

組み合わせた結果のみが必要な場合、これも機能します。

var set1 = new HashSet<T>(list1);
var set2 = new HashSet<T>(list2);
var areEqual = set1.SetEquals(set2);

ここで、Tはリスト要素のタイプです。


-1

面白いかもしれませんが、私にとってはうまくいきます

string.Join( ""、List1)!= string.Join( ""、List2)


ここで書かれているように、List <string>やList <int>でも機能しません。たとえば、2つのリスト11; 2; 3と1; 12; 3は、文字列をいくつかで結合しないので同一になります。リストで使用できない一意のセパレータ。それとは別に、多くのアイテムを含むリストの文字列を連結することは、おそらくパフォーマンスのキラーです。
SwissCoder '10 / 10/05

@SwissCoder:不正解です。これは文字列のパフォーマンスキラーではありません。50.000文字列(それぞれ長さが3)の2つのリストがある場合、このアルゴリズムは私のマシンで3 ms必要です。受け入れられた回答者には7が必要です。私はトリックはJibzが1つの文字列比較だけを必要とすると思います。もちろん、彼は独自のセパレータを追加する必要があります。
user1027167

@ user1027167:私は文字列を直接比較することについて話していません(これも問題ではありません)。50.000個のオブジェクトを持つリスト内のすべてのオブジェクトの.ToString()メソッドを呼び出すと、実装方法によっては、巨大な文字列が作成される可能性があります。それは行くべき道ではないと私は思います。次に、文字または文字列が「一意」であることを信頼することも危険であり、コードは実際にはそのように再利用できません。
スイスコーダー2018

そうだね。質問者は、自分のリストのデータ型を指定せずに最も迅速な方法を求めました。おそらく、この回答者が質問者のユースケースにとって最も速い方法です。
user1027167

-3

これは、要素ごとに2つのリストを比較するシンプルで簡単な方法だと思います

x=[1,2,3,5,4,8,7,11,12,45,96,25]
y=[2,4,5,6,8,7,88,9,6,55,44,23]

tmp = []


for i in range(len(x)) and range(len(y)):
    if x[i]>y[i]:
        tmp.append(1)
    else:
        tmp.append(0)
print(tmp)

3
これはC#の質問であり、C#コードが提供されていません。
ワイハリー

1
おそらく、この回答を削除して、(たとえば)Pythonで2つのリストを比較して一致を返すにはどうすればよいですか?
Wai Ha Lee

-4

これはあなたが見つける最高のソリューションです

var list3 = list1.Where(l => list2.ToList().Contains(l));

1
これはList<T>、の各要素に対して新しいを作成するため、実際には非常に悪いlist1です。また、結果がでlist3ない場合にも呼び出されList<T>ます。
ワイハリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.