LINQ를 사용하여 하나의 List <>에서 다른 List <>에없는 항목을 가져옵니다.


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

peopleList2에없는 사람들을 모두에게 제공하기 위해 LINQ 쿼리를 수행하고 싶습니다 peopleList1.

이 예는 두 사람을 제공해야합니다 (ID = 4 & ID = 5).


3
개체의 ID가 실시간으로 변경되지 않아야하므로 ID를 읽기 전용으로 만드는 것이 좋습니다. 물론 테스트 또는 ORM 프레임 워크는 변경이 가능해야합니다.
코드 InChaos

답변:


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) 솔루션이라는 것을 알고 있습니까?
Niki

32
@nikie, OP는 Linq를 사용하는 솔루션을 요청했습니다. 아마 그는 Linq를 배우려고 노력하고있을 것이다. 질문이 가장 효율적인 방법이라면 내 질문은 반드시 같을 필요는 없습니다.
클라우스 비 스코프 페데르센

46
@nikie, 쉬운 솔루션을 공유하고 싶으십니까?
Rubio

18
이것은 동등하며 따라하기가 더 쉽습니다. var result = peopleList2.Where (p => peopleList1.All (p2 => p2.ID! = p.ID));
AntonK

28
@ Menol-질문에 올바르게 응답하는 사람을 비판하는 것은 약간 불공평 할 수 있습니다. 사람들은 미래의 사람들이 답을 찾을 수있는 모든 방법과 상황을 예상 할 필요는 없습니다. 실제로, 당신은 그것을 제공하지 않고 대안을 알고 있다고 시간을내는 nikie에게 그것을 지시해야합니다.
Chris Rogers

396

People의 평등을 무시하면 다음을 사용할 수도 있습니다.

peopleList2.Except(peopleList1)

ExceptWhere(...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));

이 변형은 중복을 제거하지 않습니다.


EqualsID를 비교하기 위해 재정의 된 경우에만 작동합니다 .
클라우스 비 스코프 페데르센

34
그렇기 때문에 평등을 무시해야한다고 썼습니다. 그러나 나는 그것 없이도 작동하는 예제를 추가했습니다.
코드 InChaos

4
Person이 구조체라면 작동합니다. 그러나 Person은 식별하지 않는 "ID"라는 속성을 가지고 있기 때문에 불완전한 클래스로 보입니다. 식별하지 않으면 equal이 재정의되므로 equal ID는 동일한 Person을 의미합니다. Person의 버그가 수정되면이 접근 방식이 더 좋습니다 (버그가 식별자 인 것처럼 오도되지 않는 다른 것으로 "ID"의 이름을 바꾸어 수정하지 않는 한).
Jon Hanna

2
문자열 (또는 다른 기본 객체) 목록에 대해 이야기하는 경우에도 효과적입니다.이 스레드를 찾았을 때 내가 찾고 있던 것이 었습니다.
Dan Korn

@ DanKorn 이것은 기본 비교를 위해 int, objects ref, string과 비교할 때보 다 간단한 솔루션입니다.
Maze

73

또는 부정없이 원한다면 :

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

기본적으로 peopleList1의 모든 ID가 peoplesList2의 id와 다른 peopleList2에서 모두 가져옵니다.

허용 된 답변과 약간 다른 접근 방식 :)


5
이 방법 (50,000 개가 넘는 항목 목록)은 모든 방법보다 훨씬 빠릅니다!
DaveN

5
게으 르기 때문에 더 빠를 수 있습니다. 이것은 아직 실제 작업을 수행하고 있지 않습니다. 실제로 작업을 수행하는 목록을 열거 할 때까지 (ToList를 호출하거나 foreach 루프의 일부로 사용하는 등)
Xtros

32

지금까지 모든 솔루션이 유창한 구문을 사용했기 때문에 다음은 관심있는 사람들을위한 쿼리 표현식 구문의 솔루션입니다.

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

나는 그것이 일부 사람들에게 관심을 가질만한 대답과 충분히 다르다고 생각하며 심지어 목록에 대해 차선책이라고 생각했습니다. 이제 색인화 된 ID가있는 테이블의 경우 이것이 확실합니다.


감사합니다. 먼저 쿼리 표현식 구문을 방해합니다.
일반적인 이름

15

파티에 늦었지만 Linq to 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

Alrekr- "더 많은 속성을 비교해야하는 경우 더 많은 속성을 비교해야합니다"라는 말이 있다면 그것은 분명합니다.
Lucas Morgan

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

다음은 구직자에게없는 IT 기술을 익히는 실무 예제입니다.

//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

먼저 조건이있는 컬렉션에서 ID를 추출합니다.

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

둘째, "비교"estament를 사용하여 선택과 다른 ID를 선택하십시오.

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);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.