2つのコレクション内のアイテムの順序に関係なく、等しいかどうかを比較する


162

2つのコレクションを(C#で)比較したいのですが、これを効率的に実装するための最良の方法がわかりません。

Enumerable.SequenceEqualに関する他のスレッドを読みましたが、私が探しているものとは異なります

私の場合、2つのコレクションに同じアイテムが含まれている場合(順序に関係なく)、2つのコレクションは等しくなります。

例:

collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};

collection1 == collection2; // true

私が通常行うことは、1つのコレクションの各アイテムをループして他のコレクションに存在するかどうかを確認し、次に他のコレクションの各アイテムをループして最初のコレクションに存在するかどうかを確認することです。(長さを比較することから始めます)。

if (collection1.Count != collection2.Count)
    return false; // the collections are not equal

foreach (Item item in collection1)
{
    if (!collection2.Contains(item))
        return false; // the collections are not equal
}

foreach (Item item in collection2)
{
    if (!collection1.Contains(item))
        return false; // the collections are not equal
}

return true; // the collections are equal

ただし、これは完全に正しいわけではなく、おそらく2つのコレクションが等しいかどうかを比較する最も効率的な方法ではありません。

私がそれを間違っていると考えることができる例は:

collection1 = {1, 2, 3, 3, 4}
collection2 = {1, 2, 2, 3, 4}

これは私の実装と同じです。各アイテムが見つかった回数をカウントし、両方のコレクションでカウントが等しいことを確認する必要がありますか?


例はある種のC#(疑似C#と呼びましょう)ですが、任意の言語で回答を提供します。問題はありません。

注:例では簡単にするために整数を使用しましたが、参照タイプのオブジェクトも使用できるようにしたいです(オブジェクトの参照のみが比較され、内容は比較されないため、キーとして正しく動作しません)。


1
アルゴリズムはどうですか?すべての答えは、何かを比較すること、一般的なリストはlinqを比較することなどに関連しています。本当に、昔ながらのプログラマーとしてアルゴリズムを使用しないことを誰かに約束しましたか?
Nuri YILMAZ

等価性をチェックしているわけではありません。それはひどいですが重要な違いです。そしてずっと昔。これは良いQ + Aです。
CADは2015年

以下で説明する辞書ベースの方法の調整されたバージョンについて説明しているこの投稿に興味があるかもしれません。ほとんどの単純なディクショナリアプローチの1つの問題は、.NETのディクショナリクラスがnullキーを許可しないため、nullを適切に処理しないことです。
ChaseMedallion 2016年

回答:


112

マイクロソフトは既にテストフレームワークでこれをカバーしていることがわかりました:CollectionAssert.AreEquivalent

備考

2つのコレクションは、同じ数量で同じ要素を任意の順序で持っている場合、同等です。要素は、それらが同じオブジェクトを参照しているのではなく、値が等しい場合に等しくなります。

リフレクターを使用して、AreEquivalent()の背後にあるコードを変更し、対応する等価比較子を作成しました。nullを考慮し、IEqualityComparerを実装し、いくつかの効率とエッジケースチェックを備えているため、既存の回答よりも完全です。さらに、それはマイクロソフトです :)

public class MultiSetComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    private readonly IEqualityComparer<T> m_comparer;
    public MultiSetComparer(IEqualityComparer<T> comparer = null)
    {
        m_comparer = comparer ?? EqualityComparer<T>.Default;
    }

    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == null)
            return second == null;

        if (second == null)
            return false;

        if (ReferenceEquals(first, second))
            return true;

        if (first is ICollection<T> firstCollection && second is ICollection<T> secondCollection)
        {
            if (firstCollection.Count != secondCollection.Count)
                return false;

            if (firstCollection.Count == 0)
                return true;
        }

        return !HaveMismatchedElement(first, second);
    }

    private bool HaveMismatchedElement(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstNullCount;
        int secondNullCount;

        var firstElementCounts = GetElementCounts(first, out firstNullCount);
        var secondElementCounts = GetElementCounts(second, out secondNullCount);

        if (firstNullCount != secondNullCount || firstElementCounts.Count != secondElementCounts.Count)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            var firstElementCount = kvp.Value;
            int secondElementCount;
            secondElementCounts.TryGetValue(kvp.Key, out secondElementCount);

            if (firstElementCount != secondElementCount)
                return true;
        }

        return false;
    }

    private Dictionary<T, int> GetElementCounts(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>(m_comparer);
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        if (enumerable == null) throw new ArgumentNullException(nameof(enumerable));

        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + (val?.GetHashCode() ?? 42);

        return hash;
    }
}

使用例:

var set = new HashSet<IEnumerable<int>>(new[] {new[]{1,2,3}}, new MultiSetComparer<int>());
Console.WriteLine(set.Contains(new [] {3,2,1})); //true
Console.WriteLine(set.Contains(new [] {1, 2, 3, 3})); //false

または、2つのコレクションを直接比較したい場合:

var comp = new MultiSetComparer<string>();
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","c","b"})); //true
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","b"})); //false

最後に、選択した等価比較子を使用できます。

var strcomp = new MultiSetComparer<string>(StringComparer.OrdinalIgnoreCase);
Console.WriteLine(strcomp.Equals(new[] {"a", "b"}, new []{"B", "A"})); //true

7
100%確実ではありませんが、あなたの回答がマイクロソフトのリバースエンジニアリング利用規約に違反していると思います。
Ian Dallas、

1
こんにちはオハド、トピック内の次の長い議論を読んでください、stackoverflow.com / questions / 371328 /…オブジェクトのハッシュコードを変更すると、そのハッシュセット内でハッシュセットの適切なアクションが中断され、例外が発生する可能性があります。ルールは次のとおりです。2つのオブジェクトが等しい場合-それらは同じハッシュコードを持つ必要があります。2つのオブジェクトが同じハッシュコードを持っている場合-それらが等しいことは必須ではありません。ハッシュコードは、オブジェクトの存続期間全体にわたって同じでなければなりません!そのため、ICompareableとIEqualrityを強制します。
James Roeiter、2013

2
@JamesRoeiterおそらく私のコメントは誤解を招くものでした。ディクショナリは、すでに含まれているハッシュコードに遭遇すると、実際に等しいかどうかを確認しEqualityComparerます(指定したものかEqualityComparer.Default、Reflectorまたは参照ソースを確認してこれを確認できます)。True、このメソッドの実行中にオブジェクトが変更された場合(具体的にはハッシュコードが変更された場合)、結果は予想外ですが、これはこのメソッドがこのコンテキストでスレッドセーフではないことを意味します。
Ohad Schneider

1
@JamesRoeiter xとyを比較したい2つのオブジェクトだとします。それらが異なるハッシュコードを持っている場合、それらは異なることがわかり(等しいアイテムは等しいハッシュコードを持っているため)、上記の実装は正しいです。それらが同じハッシュコードを持っている場合、ディクショナリー実装は指定された(または何も指定されなかった場合)を使用して実際の等価性をチェックし、再び実装は正しいです。EqualityComparerEqualityComparer.Default
Ohad Schneider

1
@CADbloke インターフェースのEqualsため、メソッドに名前を付ける必要がありIEqualityComparer<T>ます。あなたが見なければならないのは、比較者自身の名前です。この場合、MultiSetComparerそれは理にかなっています。
Ohad Schneider

98

シンプルでかなり効率的なソリューションは、両方のコレクションを並べ替えて、それらが等しいかどうかを比較することです。

bool equal = collection1.OrderBy(i => i).SequenceEqual(
                 collection2.OrderBy(i => i));

このアルゴリズムはO(N * logN)ですが、上記のソリューションはO(N ^ 2)です。

コレクションに特定のプロパティがある場合、より高速なソリューションを実装できる場合があります。たとえば、両方のコレクションがハッシュセットの場合、それらに重複を含めることはできません。また、ハッシュセットに要素が含まれているかどうかのチェックは非常に高速です。その場合、あなたに似たアルゴリズムがおそらく最速でしょう。


1
System.Linqを使用して追加するだけです。最初に機能させる
JuniorMayhé2010年

このコードがループ内にあり、collection1が更新され、collection2は変更されないままである場合、両方のコレクションが同じオブジェクトを持っている場合でも、デバッガーはこの「等しい」変数に対してfalseを表示します。
ジュニアメイヘ

5
@Chaulky-OrderByが必要だと思います。参照:dotnetfiddle.net/jA8iwE
Brett

「上」と呼ばれる他の答えはどれですか?おそらくstackoverflow.com/a/50465/3195477
UuDdLrLrSs

32

辞書「dict」を作成し、最初のコレクションの各メンバーに対して、dict [member] ++を実行します。

次に、同じ方法で2番目のコレクションをループしますが、各メンバーに対してdict [member]-を実行します。

最後に、辞書のすべてのメンバーをループします。

    private bool SetEqual (List<int> left, List<int> right) {

        if (left.Count != right.Count)
            return false;

        Dictionary<int, int> dict = new Dictionary<int, int>();

        foreach (int member in left) {
            if (dict.ContainsKey(member) == false)
                dict[member] = 1;
            else
                dict[member]++;
        }

        foreach (int member in right) {
            if (dict.ContainsKey(member) == false)
                return false;
            else
                dict[member]--;
        }

        foreach (KeyValuePair<int, int> kvp in dict) {
            if (kvp.Value != 0)
                return false;
        }

        return true;

    }

編集:私の知る限り、これは最も効率的なアルゴリズムと同じ順序です。辞書がO(1)ルックアップを使用すると仮定すると、このアルゴリズムはO(N)です。


これはほとんど私が欲しいものです。ただし、整数を使用していない場合でも、これを実行できるようにしたいと考えています。参照オブジェクトを使用したいのですが、辞書のキーとして正しく動作しません。
mbillard 2008

モノ、あなたのアイテムが比較できない場合、あなたの質問は無意味です。辞書のキーとして使用できない場合、解決策はありません。
skolima 2008

1
Monoはキーがソートできないことを意味すると思います。しかし、ダニエルのソリューションは明らかに、ツリーではなくハッシュテーブルで実装することを意図しており、等価性テストとハッシュ関数がある限り機能します。
エリクソン2008年

もちろんヘルプのために賛成ですが、重要なポイント(私が私の回答で取り上げます)がないため、受け入れられませんでした。
mbillard 2009年

1
FWIW、あなたはこれであなたの最後のforeachループとreturn文を簡素化することができます:return dict.All(kvp => kvp.Value == 0);
タイソン・ウィリアムズ

18

これは(C#での)比較メソッドの(D.Jenningsの影響を強く受けた)一般的な実装です。

/// <summary>
/// Represents a service used to compare two collections for equality.
/// </summary>
/// <typeparam name="T">The type of the items in the collections.</typeparam>
public class CollectionComparer<T>
{
    /// <summary>
    /// Compares the content of two collections for equality.
    /// </summary>
    /// <param name="foo">The first collection.</param>
    /// <param name="bar">The second collection.</param>
    /// <returns>True if both collections have the same content, false otherwise.</returns>
    public bool Execute(ICollection<T> foo, ICollection<T> bar)
    {
        // Declare a dictionary to count the occurence of the items in the collection
        Dictionary<T, int> itemCounts = new Dictionary<T,int>();

        // Increase the count for each occurence of the item in the first collection
        foreach (T item in foo)
        {
            if (itemCounts.ContainsKey(item))
            {
                itemCounts[item]++;
            }
            else
            {
                itemCounts[item] = 1;
            }
        }

        // Wrap the keys in a searchable list
        List<T> keys = new List<T>(itemCounts.Keys);

        // Decrease the count for each occurence of the item in the second collection
        foreach (T item in bar)
        {
            // Try to find a key for the item
            // The keys of a dictionary are compared by reference, so we have to
            // find the original key that is equivalent to the "item"
            // You may want to override ".Equals" to define what it means for
            // two "T" objects to be equal
            T key = keys.Find(
                delegate(T listKey)
                {
                    return listKey.Equals(item);
                });

            // Check if a key was found
            if(key != null)
            {
                itemCounts[key]--;
            }
            else
            {
                // There was no occurence of this item in the first collection, thus the collections are not equal
                return false;
            }
        }

        // The count of each item should be 0 if the contents of the collections are equal
        foreach (int value in itemCounts.Values)
        {
            if (value != 0)
            {
                return false;
            }
        }

        // The collections are equal
        return true;
    }
}

12
いい仕事ですが、注:1.ダニエルジェニングスのソリューションとは対照的に、これはO(N)ではなくO(N ^ 2)です。これは、バーコレクションのforeachループ内のfind関数のためです。2.コードをさらに変更することなく、ICollection <T>ではなくIEnumerable <T>を受け入れるようにメソッドを一般化できます
Ohad Schneider

The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item"- 本当じゃない。アルゴリズムは誤った仮定に基づいており、機能している間は、非常に非効率的です。
アントニン・Lejsek


7

Shouldlyを使用する場合は、ContainsでShouldAllBeを使用できます。

collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};

collection1.ShouldAllBe(item=>collection2.Contains(item)); // true

そして最後に、拡張機能を記述できます。

public static class ShouldlyIEnumerableExtensions
{
    public static void ShouldEquivalentTo<T>(this IEnumerable<T> list, IEnumerable<T> equivalent)
    {
        list.ShouldAllBe(l => equivalent.Contains(l));
    }
}

更新

オプションのパラメーターがShouldBeメソッドに存在します。

collection1.ShouldBe(collection2, ignoreOrder: true); // true

1
最新バージョンbool ignoreOrderShouldBeメソッドにパラメーターがあることがわかりました。
Pier-Lionel Sgard 2017年

5

編集:私はこれが本当にセットに対してのみ機能することを私が提起した直後に気づきました-それは重複したアイテムを持つコレクションを適切に処理しません。たとえば、{1、1、2}と{2、2、1}は、このアルゴリズムの観点から等しいと見なされます。ただし、コレクションがセットである場合(またはそれらの等価性がそのように測定できる場合)、以下が役立つと思います。

私が使用するソリューションは次のとおりです。

return c1.Count == c2.Count && c1.Intersect(c2).Count() == c1.Count;

Linqは辞書に隠された処理を行うため、これもO(N)です。(コレクションが同じサイズでない場合は、O(1)です)。

Danielが提案した "SetEqual"メソッド、Igorが提案したOrderBy / SequenceEqualsメソッド、および私の提案を使用して、サニティチェックを行いました。結果は以下のとおりで、IgorのO(N * LogN)と、鉱山とダニエルのO(N)を示しています。

Linq交差コードの単純さは、それを望ましい解決策にすると思います。

__Test Latency(ms)__
N, SetEquals, OrderBy, Intersect    
1024, 0, 0, 0    
2048, 0, 0, 0    
4096, 31.2468, 0, 0    
8192, 62.4936, 0, 0    
16384, 156.234, 15.6234, 0    
32768, 312.468, 15.6234, 46.8702    
65536, 640.5594, 46.8702, 31.2468    
131072, 1312.3656, 93.7404, 203.1042    
262144, 3765.2394, 187.4808, 187.4808    
524288, 5718.1644, 374.9616, 406.2084    
1048576, 11420.7054, 734.2998, 718.6764    
2097152, 35090.1564, 1515.4698, 1484.223

このコードの唯一の問題は、値の型を比較す​​るとき、または参照型へのポインターを比較するときにのみ機能することです。コレクション内に同じオブジェクトの2つの異なるインスタンスを含めることができるため、それぞれを比較する方法を指定できるようにする必要があります。比較デリゲートをインターセクトメソッドに渡すことはできますか?
mbillard 2009年

もちろん、比較デリゲートを渡すことができます。しかし、私が追加したセットに関する上記の制限に注意してください。これは、その適用性に大きな制限を課します。

Intersectメソッドは、別個のコレクションを返します。a = {1,1,2}およびb = {2,2,1}の場合、a.Intersect(b).Count()!= a.Count。これにより、式は正しくfalseを返します。{1,2} .Count!= {1,1,2} .Count リンク [/ link]を参照してください(比較の前に両側が区別されることに注意してください。)
Griffin

5

繰り返しも順序もない場合、次のEqualityComparerを使用して、コレクションを辞書キーとして許可できます。

public class SetComparer<T> : IEqualityComparer<IEnumerable<T>> 
where T:IComparable<T>
{
    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == second)
            return true;
        if ((first == null) || (second == null))
            return false;
        return first.ToHashSet().SetEquals(second);
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}

これが私が使用したToHashSet()実装です。ハッシュコードアルゴリズムは、(ジョンスキートの方法により)効果的な、Javaから来ています。


ComparerクラスのSerializableのポイントは何ですか?:oまた、入力を変更して、ISet<T>それがセットを意味する(つまり、重複がない)ことを表すようにすることもできます。
nawfal

用として@nawfalおかげで、...私はシリアライズそれをマークしたときに、私が考えていたかわからないISet、ここでの考え方は、治療することでしたIEnumerable(あなたが得たのでセットとしてIEnumerableオーバーで0 upvotesを考慮しても、そもそもの) 5年は、最も鋭い考えではなかったかもしれません:P
オハドシュナイダー

4
static bool SetsContainSameElements<T>(IEnumerable<T> set1, IEnumerable<T> set2) {
    var setXOR = new HashSet<T>(set1);
    setXOR.SymmetricExceptWith(set2);
    return (setXOR.Count == 0);
}

ソリューションには.NET 3.5とSystem.Collections.Generic名前空間が必要です。MicrosoftよるとSymmetricExceptWithO(n + m)演算です。nは最初のセットの要素の数を表し、mは2番目のセットの要素の数を表します。必要に応じて、常にこの関数に等値比較子を追加できます。


3

.Except()を使用しない理由

// Create the IEnumerable data sources.
string[] names1 = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] names2 = System.IO.File.ReadAllLines(@"../../../names2.txt");
// Create the query. Note that method syntax must be used here.
IEnumerable<string> differenceQuery =   names1.Except(names2);
// Execute the query.
Console.WriteLine("The following lines are in names1.txt but not names2.txt");
foreach (string s in differenceQuery)
     Console.WriteLine(s);

http://msdn.microsoft.com/en-us/library/bb397894.aspx


2
Except重複アイテムのカウントには機能しません。セット{1,2,2}および{1,1,2}の場合はtrueを返します。
クリスティアンディアコネスク2013年

@CristiDiaconescuでは、最初に ".Distinct()"を実行して重複を削除できます
Korayem

OPはを求めてい[1, 1, 2] != [1, 2, 2]ます。を使用Distinctすると、それらは同等に見えます。
クリスティアンディアコネスク2018

2

ソートの重複した投稿ですが、コレクションを比較するための私のソリューションをチェックしてください。とても簡単です:

これにより、順序に関係なく等価比較が実行されます。

var list1 = new[] { "Bill", "Bob", "Sally" };
var list2 = new[] { "Bob", "Bill", "Sally" };
bool isequal = list1.Compare(list2).IsSame;

これにより、アイテムが追加または削除されたかどうかが確認されます。

var list1 = new[] { "Billy", "Bob" };
var list2 = new[] { "Bob", "Sally" };
var diff = list1.Compare(list2);
var onlyinlist1 = diff.Removed; //Billy
var onlyinlist2 = diff.Added;   //Sally
var inbothlists = diff.Equal;   //Bob

これにより、ディクショナリのどのアイテムが変更されたかがわかります。

var original = new Dictionary<int, string>() { { 1, "a" }, { 2, "b" } };
var changed = new Dictionary<int, string>() { { 1, "aaa" }, { 2, "b" } };
var diff = original.Compare(changed, (x, y) => x.Value == y.Value, (x, y) => x.Value == y.Value);
foreach (var item in diff.Different)
  Console.Write("{0} changed to {1}", item.Key.Value, item.Value.Value);
//Will output: a changed to aaa

元の投稿 こちら


1

エリクソンはほぼ正しいです。重複の数で一致させたいので、バッグが必要です。Javaでは、これは次のようになります。

(new HashBag(collection1)).equals(new HashBag(collection2))

C#にはSet実装が組み込まれているはずです。最初にそれを使用します。パフォーマンスに問題がある場合は、常に異なるSet実装を使用できますが、同じSetインターフェースを使用できます。


1

これは、誰かに役立つ場合に備えて、ohadscの回答の拡張メソッドのバリアントです

static public class EnumerableExtensions 
{
    static public bool IsEquivalentTo<T>(this IEnumerable<T> first, IEnumerable<T> second)
    {
        if ((first == null) != (second == null))
            return false;

        if (!object.ReferenceEquals(first, second) && (first != null))
        {
            if (first.Count() != second.Count())
                return false;

            if ((first.Count() != 0) && HaveMismatchedElement<T>(first, second))
                return false;
        }

        return true;
    }

    private static bool HaveMismatchedElement<T>(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstCount;
        int secondCount;

        var firstElementCounts = GetElementCounts<T>(first, out firstCount);
        var secondElementCounts = GetElementCounts<T>(second, out secondCount);

        if (firstCount != secondCount)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            firstCount = kvp.Value;
            secondElementCounts.TryGetValue(kvp.Key, out secondCount);

            if (firstCount != secondCount)
                return true;
        }

        return false;
    }

    private static Dictionary<T, int> GetElementCounts<T>(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>();
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    static private int GetHashCode<T>(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}

これはどの程度うまく機能しますか?
nawfal 2013年

私はこれを小さなコレクションにのみ使用するので、Big-Oの複雑さやベンチマークを考慮していません。HaveMismatchedElementsだけではO(M * N)であるため、大きなコレクションではうまく機能しない可能性があります。
エリックJ.

IEnumerable<T>sがクエリの場合、呼び出しCount()はお勧めできません。オハドの元の答えICollection<T>は、それらがそうであるかどうかをチェックするアプローチの方が優れています。
nawfal

1

ここでの改良であるソリューションであり、この1は

public static bool HasSameElementsAs<T>(
        this IEnumerable<T> first, 
        IEnumerable<T> second, 
        IEqualityComparer<T> comparer = null)
    {
        var firstMap = first
            .GroupBy(x => x, comparer)
            .ToDictionary(x => x.Key, x => x.Count(), comparer);

        var secondMap = second
            .GroupBy(x => x, comparer)
            .ToDictionary(x => x.Key, x => x.Count(), comparer);

        if (firstMap.Keys.Count != secondMap.Keys.Count)
            return false;

        if (firstMap.Keys.Any(k1 => !secondMap.ContainsKey(k1)))
            return false;

        return firstMap.Keys.All(x => firstMap[x] == secondMap[x]);
    }

0

この問題には多くの解決策があります。重複を気にしない場合は、両方を並べ替える必要はありません。まず、アイテムの数が同じであることを確認します。その後、コレクションの1つをソートします。次に、ソートされたコレクションの2番目のコレクションの各項目をビンサーチします。指定されたアイテムが見つからない場合は、停止してfalseを返します。これの複雑さ:-最初のコレクションのソート:N Log(N)-2番目から最初の各アイテムを検索:NLOG(N)なので、2 * N * LOG(N)と一致し、すべてを検索すると仮定します。これは、両方の並べ替えの複雑さに似ています。また、違いがある場合は、これにより早く停止することができます。ただし、この比較に入る前に両方を並べ替えて、qsortなどを使用して並べ替えを試みると、並べ替えのコストが高くなることに注意してください。これには最適化があります。要素の範囲がわかっている小さなコレクションに最適なもう1つの方法は、ビットマスクインデックスを使用することです。これにより、O(n)のパフォーマンスが得られます。別の方法は、ハッシュを使用してそれを調べることです。小さなコレクションの場合、通常はソートまたはビットマスクインデックスを実行する方がはるかに優れています。ハッシュテーブルは局所性が悪いという欠点があるので注意してください。繰り返しますが、それはあなたがしない場合にのみです 重複に注意してください。重複を考慮したい場合は、両方を並べ替えてください。


0

多くの場合、唯一の適切な答えはIgor Ostrovskyの答えですが、他の答えはオブジェクトのハッシュコードに基づいています。しかし、オブジェクトのハッシュコードを生成するときは、オブジェクトIDフィールド(データベースエンティティの場合)など、IMMUTABLEフィールドに基づいてのみ生成します。Equals メソッドがオーバーライドされているときにGetHashCodeをオーバーライドすることが重要なのはなぜですか。

つまり、2つのコレクションを比較する場合、異なる項目のフィールドが等しくなくても、compareメソッドの結果はtrueになる可能性があります。コレクションを詳細に比較するには、Igorのメソッドを使用してIEqualirityを実装する必要があります。

私とシュナイダー氏の最も投票された投稿のコメントを読んでください。

ジェームズ


0

での重複を許可しIEnumerable<T>(セットが望ましくない場合)、「順序を無視」すると、を使用できるようになります.GroupBy()

私は複雑さの測定の専門家ではありませんが、私の初歩的な理解では、これはO(n)である必要があります。O(n ^ 2)は、のような別のO(n)操作内でO(n)操作を実行することから生じるものとして理解していListA.Where(a => ListB.Contains(a)).ToList()ます。ListBのすべてのアイテムは、ListAの各アイテムと等しいかどうか評価されます。

私が言ったように、複雑さに関する私の理解は限られているので、私が間違っている場合はこれを修正してください。

public static bool IsSameAs<T, TKey>(this IEnumerable<T> source, IEnumerable<T> target, Expression<Func<T, TKey>> keySelectorExpression)
    {
        // check the object
        if (source == null && target == null) return true;
        if (source == null || target == null) return false;

        var sourceList = source.ToList();
        var targetList = target.ToList();

        // check the list count :: { 1,1,1 } != { 1,1,1,1 }
        if (sourceList.Count != targetList.Count) return false;

        var keySelector = keySelectorExpression.Compile();
        var groupedSourceList = sourceList.GroupBy(keySelector).ToList();
        var groupedTargetList = targetList.GroupBy(keySelector).ToList();

        // check that the number of grouptings match :: { 1,1,2,3,4 } != { 1,1,2,3,4,5 }
        var groupCountIsSame = groupedSourceList.Count == groupedTargetList.Count;
        if (!groupCountIsSame) return false;

        // check that the count of each group in source has the same count in target :: for values { 1,1,2,3,4 } & { 1,1,1,2,3,4 }
        // key:count
        // { 1:2, 2:1, 3:1, 4:1 } != { 1:3, 2:1, 3:1, 4:1 }
        var countsMissmatch = groupedSourceList.Any(sourceGroup =>
                                                        {
                                                            var targetGroup = groupedTargetList.Single(y => y.Key.Equals(sourceGroup.Key));
                                                            return sourceGroup.Count() != targetGroup.Count();
                                                        });
        return !countsMissmatch;
    }


0

ユニットテストのアサーションの目的で比較する場合、ウィンドウからある程度の効率を上げて、比較を行う前に各リストを文字列表現(csv)に変換することは理にかなっています。このように、デフォルトのテストアサーションメッセージは、エラーメッセージ内の違いを表示します。

使用法:

using Microsoft.VisualStudio.TestTools.UnitTesting;

// define collection1, collection2, ...

Assert.Equal(collection1.OrderBy(c=>c).ToCsv(), collection2.OrderBy(c=>c).ToCsv());

ヘルパー拡張メソッド:

public static string ToCsv<T>(
    this IEnumerable<T> values,
    Func<T, string> selector,
    string joinSeparator = ",")
{
    if (selector == null)
    {
        if (typeof(T) == typeof(Int16) ||
            typeof(T) == typeof(Int32) ||
            typeof(T) == typeof(Int64))
        {
            selector = (v) => Convert.ToInt64(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(decimal))
        {
            selector = (v) => Convert.ToDecimal(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(float) ||
                typeof(T) == typeof(double))
        {
            selector = (v) => Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            selector = (v) => v.ToString();
        }
    }

    return String.Join(joinSeparator, values.Select(v => selector(v)));
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.