Сравните два списка на предмет различий - PullRequest
44 голосов
/ 24 марта 2009

Я хотел бы получить отзыв о том, как лучше всего написать универсальную функцию, которая позволит сравнивать два списка. Списки содержат объекты класса, и мы хотели бы перебрать один список, найти тот же элемент во втором Списке и сообщить о любых различиях.

У нас уже есть метод для сравнения классов, поэтому нам нужна обратная связь о том, как мы можем использовать метод (показанный ниже) из двух списков.

Например, скажем, у нас есть простой класс «Сотрудник», который имеет три свойства: Имя, Идентификатор, Отдел. Мы хотим сообщить о различиях между списком и другим списком.

Примечание:
Оба списка всегда будут содержать одинаковое количество элементов.

Как упомянуто выше, у нас есть универсальный метод, который мы используем для сравнения двух классов, как мы можем включить этот метод для обслуживания списков, то есть из другого метода, выполнить цикл по списку и передать классы в универсальный метод .. .. но как нам найти эквивалентный класс во втором Списке, чтобы перейти к методу ниже;

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 не может быть и речи.

Ответы [ 5 ]

72 голосов
/ 09 мая 2011

Это решение создает список результатов, который содержит все отличия от обоих входных списков. Вы можете сравнить ваши объекты по любому свойству, в моем примере это ID. Единственное ограничение - списки должны быть одного типа:

var DifferencesList = ListA.Where(x => !ListB.Any(x1 => x1.id == x.id))
            .Union(ListB.Where(x => !ListA.Any(x1 => x1.id == x.id)));
15 голосов
/ 24 марта 2009

.... но как найти эквивалентный класс во втором списке для передачи в метод ниже;

Это ваша актуальная проблема; у вас должно быть хотя бы одно неизменное свойство, идентификатор или что-то подобное, чтобы идентифицировать соответствующие объекты в обоих списках. Если у вас нет такой собственности, вы не сможете решить проблему без ошибок. Вы можете просто попытаться угадать соответствующие объекты путем поиска минимальных или логических изменений.

Если у вас есть такое свойство, решение становится действительно простым.

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

Спасибо вам, Данбрук и Нолдорин за ваш отзыв. оба списка будут одинаковыми длина и в том же порядке. так что метод выше близок, но вы можете изменить это метод для передачи 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.

6 голосов
/ 24 марта 2009

Я думаю, вы ищете такой метод:

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);
    }
}

Это не проверено, но, тем не менее, оно должно выполнять свою работу. Обратите внимание, что в этом методе особенно полезно то, что он полностью универсален, то есть может принимать две последовательности произвольных (и разных) типов и возвращать объекты любого типа.

Это решение, конечно, предполагает, что вы хотите сравнить n-й элемент seq1 с n-м элементом в seq2. Если вы хотите сопоставить элементы в двух последовательностях на основе определенного свойства / сравнения, то вам нужно выполнить какую-то операцию join (как предложено danbruc с использованием Enumerable.Join. я знаю, если это не тот подход, к которому я стремлюсь, и, возможно, я могу предложить что-то еще.

Изменить: Вот пример того, как вы можете использовать метод 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());
}
2 голосов
/ 18 июня 2013

Этот подход от Microsoft работает очень хорошо и дает возможность сравнивать один список с другим и переключать их, чтобы получить разницу в каждом. Если вы сравниваете классы, просто добавьте ваши объекты в два отдельных списка, а затем запустите сравнение.

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

1 голос
/ 24 марта 2009

Я надеюсь, что правильно понимаю ваш вопрос, но вы можете сделать это очень быстро с Linq. Я предполагаю, что всегда у вас всегда будет свойство 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);
        }
...