Разница между двумя списками


118

У меня есть два общих списка, заполненных таможенными объектами.

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

Я думал, что использование .Except()было хорошей идеей, но я не знаю, как это использовать ... Помогите!

c# 

Ответы:


237

Использование Except- это правильный путь. Если ваш тип переопределяет Equalsи GetHashCode, или вас интересует только равенство ссылочных типов (т. Е. Две ссылки «равны», только если они относятся к одному и тому же объекту), вы можете просто использовать:

var list3 = list1.Except(list2).ToList();

Если вам нужно выразить собственное представление о равенстве, например, по идентификатору, вам необходимо реализовать IEqualityComparer<T>. Например:

public class IdComparer : IEqualityComparer<CustomObject>
{
    public int GetHashCode(CustomObject co)
    {
        if (co == null)
        {
            return 0;
        }
        return co.Id.GetHashCode();
    }

    public bool Equals(CustomObject x1, CustomObject x2)
    {
        if (object.ReferenceEquals(x1, x2))
        {
            return true;
        }
        if (object.ReferenceEquals(x1, null) ||
            object.ReferenceEquals(x2, null))
        {
            return false;
        }
        return x1.Id == x2.Id;
    }
}

Затем используйте:

var list3 = list1.Except(list2, new IdComparer()).ToList();

Обратите внимание, что это удалит все повторяющиеся элементы. Если вам нужно сохранить дубликаты, вероятно, будет проще создать набор list2и использовать что-то вроде:

var list3 = list1.Where(x => !set2.Contains(x)).ToList();

18
В качестве отступления я бы добавил, что Exceptэто операция Set , тогда результирующий список будет иметь разные значения, например, {'A','A','B','C'}.Except({'B','C'}) возврат{'A'}
— digEmAll

4
У меня есть это: {'A', 'A', 'B', 'C'}. За исключением того, что ({'B', 'C'}) возвращает {'A'}, который работает ... однако {'B' , 'C'}. За исключением ({'A', 'B', 'C'}) НЕ возвращает {'A'}
— MrLister

2
Разница между двумя наборами определяется как элементы первого набора, которые не появляются во втором наборе (цитируя MS). Итак, {B, C} .Except ({A, B, C}) не должен возвращать ничего, поскольку B и C оба находятся во втором наборе. Это не ошибка, больше связано с математическим определением функции.
— Стив Хибберт

Итак, @JonSkeet, если я хочу сравнить два списка на основе, скажем, двух свойств, я могу написать этот способ сравнения и получить элементы, которые не равны, верно?
— Ehsan Sajjad

1
@NetMage: ОП заявили, что им нужны «предметы, которые находятся в первом, но не во втором» - для меня это звучит как разница в наборах. Если первый список содержит {5, 5, 5, 5, 1}, а второй список содержит {5}, тогда только 1 находится в первом списке, но не во втором.
— Джон Скит

74

Вы можете сделать что-то вроде этого:

var result = customlist.Where(p => !otherlist.Any(l => p.someproperty == l.someproperty));

это сэкономило мне один цикл foreach.
— alice7 02

12
Разве одно из этих «l.someproperty» не должно быть «p.someproperty»?
— Manos Dilaverakis

6
Это можно упростить с помощью customlist.Where (p => otherlist.Any (l => p.someproperty! = L.someproperty));
— Dhanuka777

@ Dhanuka777, это неверно. AnyДолжно быть , Allесли вы хотите сделать это так. Это связано с тем, что во втором списке может быть элемент из первого списка, но если он не проверяется первым, он немедленно получит истинное значение для p.someproperty != l.someproperty. В результате возвращаются элементы, которые существуют в обоих списках. Позор тем 6 людям, которые проголосовали за это.
— Murphybro2 05

26

Я считаю важным подчеркнуть - использование метода Except вернет вам элементы, которые находятся в первом, без элементов только во втором. Он не возвращает те элементы во втором, которые не появляются в первом.

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2).ToList(); //list3 contains only 1, 2

Но если вы хотите получить реальную разницу между двумя списками:

Элементы, которые находятся в первом, без элементов во втором и элементы, которые находятся во втором, без элементов в первом.

Вам нужно использовать Except дважды:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2); //list3 contains only 1, 2
var list4 = list2.Except(list1); //list4 contains only 6, 7
var resultList = list3.Concat(list4).ToList(); //resultList contains 1, 2, 6, 7

Или вы можете использовать метод SymmetricExceptWith HashSet. Но это меняет набор, на котором вызывается:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list1Set = list1.ToHashSet(); //.net framework 4.7.2 and .net core 2.0 and above otherwise new HashSet(list1)
list1Set.SymmetricExceptWith(list2);
var resultList = list1Set.ToList(); //resultList contains 1, 2, 6, 7

Это полезно. Просто имейте в виду, что OP дал понять, что они не хотят полной разницы: «Мне нужно получить разницу между этими двумя списками ( элементы, которые находятся в первом, без элементов во втором )».
— rsenna

10
var third = first.Except(second);

(вы также можете позвонить ToList()после Except(), если вам не нравится ссылаться на ленивые коллекции.)

Except()Метод сравнивает значения , используя компаратор по умолчанию, если значения сравниваются имеют базовых типов данных, таких , как int, string, и decimalт.д.

В противном случае сравнение будет производиться по адресу объекта, что, вероятно, не то, что вы хотите ... В этом случае сделайте свои настраиваемые объекты реализуемыми IComparable(или реализуйте настраиваемые IEqualityComparerи передайте их Except()методу).


3
var list3 = list1.Where(x => !list2.Any(z => z.Id == x.Id)).ToList();

Примечание: list3будет содержать элементы или объекты, которых нет в обоих списках. Примечание: это ToList()неtoList()


1

Поскольку метод расширения Except работает с двумя IEumerables, мне кажется, что это будет операция O (n ^ 2). Если производительность является проблемой (если, скажем, ваши списки большие), я бы предложил создать HashSet из list1 и использовать метод HashSet ExceptWith.


9
Enumerable.Exceptвнутренне использует HashSetили что-то подобное. Он определенно не использует наивный алгоритм O (n ^ 2).
— Джим Мишель,

@Jim Mischel: Вы правы, Enumerable.Except использует внутреннюю структуру данных Set и добавляет элементы из обоих IEnumerables в Set. Хотелось бы, чтобы в документации что-то говорилось об этом.
— foson

1

вот мое решение:

    List<String> list1 = new List<String>();

    List<String> list2 = new List<String>();

    List<String> exceptValue = new List<String>();

foreach(String L1 in List1) 
{
    if(!List2.Contains(L1)
    {
         exceptValue.Add(L1);
    }
}
foreach(String L2 in List2) 
{
    if(!List1.Contains(L2)
    {
         exceptValue.Add(L2);
    }
}

-1

немного поздно, но вот рабочее решение для меня

 var myBaseProperty = (typeof(BaseClass)).GetProperties();//get base code properties
                    var allProperty = entity.GetProperties()[0].DeclaringType.GetProperties();//get derived class property plus base code as it is derived from it
                    var declaredClassProperties = allProperty.Where(x => !myBaseProperty.Any(l => l.Name == x.Name)).ToList();//get the difference

В приведенном выше коде я вижу разницу свойств между моим базовым классом и списком производных классов.


-1
var resultList = checklist.Where(p => myList.All(l => p.value != l.value)).ToList();

Примечание. Список результатов - это элементы, которые существуют в контрольном списке, но отсутствуют в моем списке
— Teezy7,

-2
List<ObjectC> _list_DF_BW_ANB = new List<ObjectC>();    
List<ObjectA> _listA = new List<ObjectA>();
List<ObjectB> _listB = new List<ObjectB>();

foreach (var itemB in _listB )
{     
    var flat = 0;
    foreach(var itemA in _listA )
    {
        if(itemA.ProductId==itemB.ProductId)
        {
            flat = 1;
            break;
        }
    }
    if (flat == 0)
    {
        _list_DF_BW_ANB.Add(itemB);
    }
}

Через 6 лет после принятого ответа и ничего нового ценного не добавлено
— Митч Уит

-3

Если оба ваших списка реализуют интерфейс IEnumerable, вы можете добиться этого с помощью LINQ.

list3 = list1.where(i => !list2.contains(i));

6
И если каждый ваш список содержит миллион элементов, вам придется долго ждать. IEnumerable.ExceptВместо этого используйте .
— Джим Мишель,

3
Ага. У меня всего около 450 предметов, и я все еще жду. Будьте осторожны, используя это.
— Marnee KG7SIO

-3
        List<int> list1 = new List<int>();
        List<int> list2 = new List<int>();
        List<int> listDifference = new List<int>();

        foreach (var item1 in list1)
        {
            foreach (var item2 in list2)
            {
                if (item1 != item2)
                    listDifference.Add(item1);
            }
        }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.