Використовуйте LINQ, щоб отримати елементи в одному списку <>, які не є в іншому списку <>


525

Я б припустив, що для цього є простий запит LINQ, я просто не точно впевнений, як.

Враховуючи цей фрагмент коду:

class Program
{
    static void Main(string[] args)
    {
        List<Person> peopleList1 = new List<Person>();
        peopleList1.Add(new Person() { ID = 1 });
        peopleList1.Add(new Person() { ID = 2 });
        peopleList1.Add(new Person() { ID = 3 });

        List<Person> peopleList2 = new List<Person>();
        peopleList2.Add(new Person() { ID = 1 });
        peopleList2.Add(new Person() { ID = 2 });
        peopleList2.Add(new Person() { ID = 3 });
        peopleList2.Add(new Person() { ID = 4 });
        peopleList2.Add(new Person() { ID = 5 });
    }
}

class Person
{
    public int ID { get; set; }
}

Я хотів би виконати запит LINQ, щоб дати мені всіх людей, у peopleList2яких немаєpeopleList1 .

Цей приклад повинен дати мені дві людини (ID = 4 & ID = 5)


3
Можливо, це гарна ідея зробити ідентифікацію читати повністю, оскільки особа об'єкта не повинна змінюватися протягом його часу. Якщо, звичайно, ваша тестувальна або ORM-рамка не вимагає змінити її.
CodesInChaos

2
Чи можемо ми назвати це "лівим (або правим) виключенням приєднання" відповідно до цієї схеми?
Червоний горох

Відповіді:


911

Це можна вирішити, використовуючи такий вираз LINQ:

var result = peopleList2.Where(p => !peopleList1.Any(p2 => p2.ID == p.ID));

Альтернативний спосіб виразити це через LINQ, який деякі розробники вважають більш читаним:

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));

Попередження: Як зазначено в коментарях, ці підходи передбачають операцію O (n * m) . Це може бути добре, але це може спричинити ефективність роботи, особливо, якщо набір даних досить великий. Якщо це не відповідає вашим вимогам щодо продуктивності, можливо, вам доведеться оцінити інші варіанти. Оскільки заявлена ​​вимога є рішенням у LINQ, однак ці варіанти тут не вивчені. Як завжди, оцініть будь-який підхід щодо вимог щодо ефективності, які може мати ваш проект.


34
Ви знаєте, що це рішення проблеми O (n * m), яку можна легко вирішити за O (n + m) час?
Нікі

32
@nikie, ОП попросило рішення, яке використовує Linq. Можливо, він намагається навчитися Лінка. Якби питання було найефективнішим, моє запитання не було б таким самим.
Клаус Бисков Педерсен

46
@nikie, хочете поділитися своїм простим рішенням?
Рубіо

18
Це еквівалентно, і мені легше слідувати: var result = peopleList2.Where (p => peopleList1.All (p2 => p2.ID! = P.ID));
AntonK

28
@Menol - може бути трохи несправедливо критикувати того, хто правильно відповідає на запитання. Людям не потрібно передбачати всі шляхи та контексти, на які майбутні люди можуть натрапити на відповідь. Насправді, ви повинні скеровувати це до нікея - який узяв час заявити, що знає альтернативу, не надаючи її.
Кріс Роджерс

396

Якщо ви переважаєте рівність людей, ви також можете використовувати:

peopleList2.Except(peopleList1)

Exceptмає бути значно швидшим, ніж Where(...Any)варіант, оскільки він може помістити другий список у хештел. Where(...Any)має час виконання, O(peopleList1.Count * peopleList2.Count)тоді як варіанти, засновані на HashSet<T>(майже), мають тривалість виконання O(peopleList1.Count + peopleList2.Count).

Exceptнеявно видаляє дублікати. Це не повинно впливати на ваш випадок, але це може бути проблемою для подібних справ.

Або якщо ви хочете швидкого коду, але не хочете перекривати рівність:

var excludedIDs = new HashSet<int>(peopleList1.Select(p => p.ID));
var result = peopleList2.Where(p => !excludedIDs.Contains(p.ID));

Цей варіант не видаляє дублікати.


Це спрацює лише в тому випадку, якщо Equalsйого відміняють для порівняння ідентифікаційних номерів.
Клаус Бисков Педерсен

34
Ось чому я написав, що вам потрібно подолати рівність. Але я додав приклад, який працює навіть без цього.
CodesInChaos

4
Це також спрацювало, якби Person був структуром. Як би це не було, Person здається неповним класом, оскільки він має властивість під назвою "ID", яка не ідентифікує його - якби він ідентифікував його, то дорівнює рівню, так що рівний ідентифікатор означав рівну Особу. Після того, як виправлена ​​помилка в Person, цей підхід тоді буде кращим (якщо тільки помилка не буде виправлена ​​шляхом перейменування "ID" на щось інше, що не вводить в оману, здаючись ідентифікатором).
Джон Ханна

2
Він також чудово працює, якщо ви говорите про список рядків (або інших базових об'єктів), який я шукав, коли натрапив на цю нитку.
Дан Корн

@DanKorn Так само, це більш просте рішення, порівняно з тим, де, для базового порівняння, int, ref ref, string.
Лабіринт

73

Або якщо ви хочете без заперечень:

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));

В основному, він говорить отримати все від peopleList2, де всі ідентифікатори в peopleList1 відрізняються від id у NationsList2.

Просто трохи інший підхід від прийнятої відповіді :)


5
Цей метод (перелік понад 50 000 предметів) був значно швидшим, ніж будь-який метод!
DaveN

5
Це може бути швидше лише тому, що ліниво. Зауважте, що це ще не робить жодної реальної роботи. Це поки ви не перерахуєте список, що він насправді виконує роботу (зателефонувавши в ToList або використовуючи його як частину циклу передбачення тощо)
Xtros

32

Оскільки в усіх рішеннях на сьогоднішній день використовується синтаксис вільного слова, тут є рішення в синтаксисі вираження запитів, для тих, хто цікавиться:

var peopleDifference = 
  from person2 in peopleList2
  where !(
      from person1 in peopleList1 
      select person1.ID
    ).Contains(person2.ID)
  select person2;

Я думаю, що він досить відрізняється від наданих відповідей, щоб когось цікавити, навіть думав, що це, швидше за все, буде неоптимальним для Списків. Тепер для таблиць з індексованими ідентифікаторами це, безумовно, було б шляхом.


Дякую. Перша відповідь, яка турбує синтаксис вираження запитів.
Родова назва

15

Трохи запізнюється на вечірку, але хорошим рішенням, яке також сумісне з Linq для SQL, є:

List<string> list1 = new List<string>() { "1", "2", "3" };
List<string> list2 = new List<string>() { "2", "4" };

List<string> inList1ButNotList2 = (from o in list1
                                   join p in list2 on o equals p into t
                                   from od in t.DefaultIfEmpty()
                                   where od == null
                                   select o).ToList<string>();

List<string> inList2ButNotList1 = (from o in list2
                                   join p in list1 on o equals p into t
                                   from od in t.DefaultIfEmpty()
                                   where od == null
                                   select o).ToList<string>();

List<string> inBoth = (from o in list1
                       join p in list2 on o equals p into t
                       from od in t.DefaultIfEmpty()
                       where od != null
                       select od).ToList<string>();

Зверніть увагу на http://www.dotnet-tricks.com/Tutorial/linq/UXPF181012-SQL-Joins-with-C


12

Відповідь Клауса була чудовою, але ReSharper попросить вас "Спростити вираз LINQ":

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));


Варто зазначити, що цей трюк не буде працювати, якщо два об'єкти зв'язують два об'єкти (думаю, що складений ключ SQL).
Алрекр

Alrekr - Якщо ви хочете сказати, що "вам потрібно порівняти більше властивостей, якщо потрібно порівняти більше властивостей", то я б сказав, що це досить очевидно.
Лукас Морган

8

Це незліченне розширення дозволяє визначити список елементів для виключення та функцію, яку потрібно знайти для використання ключа для порівняння.

public static class EnumerableExtensions
{
    public static IEnumerable<TSource> Exclude<TSource, TKey>(this IEnumerable<TSource> source,
    IEnumerable<TSource> exclude, Func<TSource, TKey> keySelector)
    {
       var excludedSet = new HashSet<TKey>(exclude.Select(keySelector));
       return source.Where(item => !excludedSet.Contains(keySelector(item)));
    }
}

Ви можете використовувати це таким чином

list1.Exclude(list2, i => i.ID);

Маючи код, який має @BrianT, як я можу його перетворити, щоб використовувати ваш код?
Nicke Manarin

0

Ось робочий приклад, який отримує ІТ-навички, яких кандидат у роботу ще не має.

//Get a list of skills from the Skill table
IEnumerable<Skill> skillenum = skillrepository.Skill;
//Get a list of skills the candidate has                   
IEnumerable<CandSkill> candskillenum = candskillrepository.CandSkill
       .Where(p => p.Candidate_ID == Candidate_ID);             
//Using the enum lists with LINQ filter out the skills not in the candidate skill list
IEnumerable<Skill> skillenumresult = skillenum.Where(p => !candskillenum.Any(p2 => p2.Skill_ID == p.Skill_ID));
//Assign the selectable list to a viewBag
ViewBag.SelSkills = new SelectList(skillenumresult, "Skill_ID", "Skill_Name", 1);

0

спочатку витягніть ідентифікатори з колекції, де умова

List<int> indexes_Yes = this.Contenido.Where(x => x.key == 'TEST').Select(x => x.Id).ToList();

по-друге, використовуйте властивість "зіставити", щоб вибрати ідентифікатори, що відрізняються від вибору

List<int> indexes_No = this.Contenido.Where(x => !indexes_Yes.Contains(x.Id)).Select(x => x.Id).ToList();

Очевидно, ви можете використовувати x.key! = "TEST", але це лише приклад


0

Після того як ви напишете загальний FuncEqualityComparer, ви можете використовувати його скрізь.

peopleList2.Except(peopleList1, new FuncEqualityComparer<Person>((p, q) => p.ID == q.ID));

public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, T, bool> comparer;
    private readonly Func<T, int> hash;

    public FuncEqualityComparer(Func<T, T, bool> comparer)
    {
        this.comparer = comparer;
        if (typeof(T).GetMethod(nameof(object.GetHashCode)).DeclaringType == typeof(object))
            hash = (_) => 0;
        else
            hash = t => t.GetHashCode(); 
    }

    public bool Equals(T x, T y) => comparer(x, y);
    public int GetHashCode(T obj) => hash(obj);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.