web-dev-qa-db-ja.com

2つのリストの違いを比較する

2つのリストの比較を可能にする汎用関数をどのように作成するのが最善かについて、フィードバックをお願いします。リストにはクラスオブジェクトが含まれており、1つのリストを反復処理して、2番目のリストで同じアイテムを探し、相違点を報告したいと思います。

クラスを比較するメソッドはすでにあるので、2つのリストからメソッド(以下に示す)をフィードする方法についてのフィードバックが必要です。

たとえば、Name、ID、Departmentという3つのプロパティを持つ単純な「Employee」クラスがあるとします。リストと別のリストの違いを報告したいと思います。

注意:
両方のリストには常に同じ数のアイテムが含まれます。

上記のように、2つのクラスを比較するために使用するジェネリックメソッドがあります。リストに対応するために、つまり別のメソッドからリストをループし、クラスをジェネリックメソッドにフィードするためにこのメソッドを組み込む方法2番目のリストで同等のクラスを見つけて、以下のメソッドに渡すにはどうすればよいですか。

public static string CompareTwoClass_ReturnDifferences<T1, T2>(T1 Orig, T2 Dest)
    where T1 : class
    where T2 : class
{
    // Instantiate if necessary
    if (Dest == null) throw new ArgumentNullException("Dest", "Destination class must first be instantiated.");

    var Differences = CoreFormat.StringNoCharacters;

    // Loop through each property in the destination  
    foreach (var DestProp in Dest.GetType().GetProperties())
    {
        // Find the matching property in the Orig class and compare
        foreach (var OrigProp in Orig.GetType().GetProperties())
        {

            if (OrigProp.Name != DestProp.Name || OrigProp.PropertyType != DestProp.PropertyType) continue;
            if (OrigProp.GetValue(Orig, null).ToString() != DestProp.GetValue(Dest, null).ToString())
                Differences = Differences == CoreFormat.StringNoCharacters 
                    ? string.Format("{0}: {1} -> {2}", OrigProp.Name,
                                                       OrigProp.GetValue(Orig, null),
                                                       DestProp.GetValue(Dest, null)) 
                    : string.Format("{0} {1}{2}: {3} -> {4}", Differences,
                                                              Environment.NewLine,
                                                              OrigProp.Name,
                                                              OrigProp.GetValue(Orig, null),
                                                              DestProp.GetValue(Dest, null));
        }
    }
    return Differences;
}

提案やアイデアはありがたいですか?

編集:.NET 2.0をターゲットにして、LINQが問題にならないようにします。

44
pedro

....しかし、2番目のリストで同等のクラスを見つけて、以下のメソッドに渡すにはどうすればよいですか。

これが実際の問題です。両方のリストで対応するオブジェクトを識別するために、少なくとも1つの不変プロパティ、idなどが必要です。そのようなプロパティがない場合、エラーなしで問題を解決することはできません。最小限または論理的な変更を検索するだけで、対応するオブジェクトを推測することができます。

このようなプロパティがある場合、ソリューションは本当に簡単になります。

Enumerable.Join(
   listA, listB,
   a => a.Id, b => b.Id,
   (a, b) => CompareTwoClass_ReturnDifferences(a, b))

ご意見をお寄せいただき、danbrucとNoldorinの両方に感謝します。両方のリストは同じ長さと同じ順序になります。上記のメソッドは近いですが、このメソッドを変更して、enum.Currentを上記のメソッドに渡すことはできますか?

今、私は混乱しています...それに関する問題は何ですか?なぜ次だけではないのですか?

for (Int32 i = 0; i < Math.Min(listA.Count, listB.Count); i++)
{
    yield return CompareTwoClass_ReturnDifferences(listA[i], listB[i]);
}

等しい長さが保証されている場合、Math.Min()呼び出しは省略されることもあります。


Noldorinの実装は、デリゲートとICollectionを使用する代わりに列挙子を使用するため、もちろんよりスマートです。

15

このソリューションは、両方の入力リストからのすべての違いを含む結果リストを作成します。任意のプロパティでオブジェクトを比較できます。私の例ではIDです。唯一の制限は、リストが同じタイプであることです。

var DifferencesList = ListA.Where(x => !ListB.Any(x1 => x1.id == x.id))
            .Union(ListB.Where(x => !ListA.Any(x1 => x1.id == x.id)));
72
Artur

次のような方法を探していると思います:

public static IEnumerable<TResult> CompareSequences<T1, T2, TResult>(IEnumerable<T1> seq1,
    IEnumerable<T2> seq2, Func<T1, T2, TResult> comparer)
{
    var enum1 = seq1.GetEnumerator();
    var enum2 = seq2.GetEnumerator();

    while (enum1.MoveNext() && enum2.MoveNext())
    {
        yield return comparer(enum1.Current, enum2.Current);
    }
}

それはテストされていませんが、それでも仕事をするはずです。このメソッドで特に役立つのは、完全に汎用であるということです。つまり、任意の(および異なる)タイプの2つのシーケンスを取り、任意のタイプのオブジェクトを返すことができます。

もちろん、このソリューションでは、seq1のn番目のアイテムとseq2のn番目のアイテムを比較することを想定しています。特定のプロパティ/比較に基づいて2つのシーケンスの要素を一致させたい場合は、何らかの種類の join 操作を実行する必要があります(Enumerable.Joinを使用してdanbrucが提案するとおり) 。これらのアプローチのどちらも私が望んでいるものではなく、他の何かを提案できるかどうか教えてください。

編集:投稿した比較関数でCompareSequencesメソッドを使用する方法の例を次に示します。

// Prints out to the console all the results returned by the comparer function (CompareTwoClass_ReturnDifferences in this case).
var results = CompareSequences(list1, list2, CompareTwoClass_ReturnDifferences);
int index;    

foreach(var element in results)
{
    Console.WriteLine("{0:#000} {1}", index++, element.ToString());
}
6
Noldorin

Microsoftのこのアプローチは非常にうまく機能し、1つのリストを別のリストと比較し、それらを切り替えてそれぞれの違いを取得するオプションを提供します。クラスを比較する場合は、オブジェクトを2つの個別のリストに追加してから比較を実行します。

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

2
user2284452

私はあなたの質問を正しく理解していることを望んでいますが、Linqを使えば非常に迅速にこれを行うことができます。普遍的に、常にIdプロパティを持っていると仮定しています。これを確実にするためにインターフェースを作成するだけです。

オブジェクトを識別する方法がクラスごとに変わる場合、2つのオブジェクトが同じ永続IDを持っている場合にtrueを返すデリゲートを渡すことをお勧めします。

Linqで行う方法は次のとおりです。

List<Employee> listA = new List<Employee>();
        List<Employee> listB = new List<Employee>();

        listA.Add(new Employee() { Id = 1, Name = "Bill" });
        listA.Add(new Employee() { Id = 2, Name = "Ted" });

        listB.Add(new Employee() { Id = 1, Name = "Bill Sr." });
        listB.Add(new Employee() { Id = 3, Name = "Jim" });

        var identicalQuery = from employeeA in listA
                             join employeeB in listB on employeeA.Id equals employeeB.Id
                             select new { EmployeeA = employeeA, EmployeeB = employeeB };

        foreach (var queryResult in identicalQuery)
        {
            Console.WriteLine(queryResult.EmployeeA.Name);
            Console.WriteLine(queryResult.EmployeeB.Name);
        }
1
Agrr