C#/LINQ

[C#]LINQ 교집합 구하는 방법 - Intersect 메서드

DevStory 2022. 7. 14.

Intersect 메서드

LINQ의 Intersect() 메서드는 두 컬렉션에서 동일한 요소를 추출합니다. 예를 들어 다음 소스 코드를 봅시다.

int[] intArrayA = { 0, 2, 4, 5 };
int[] intArrayB = { 1, 2, 3, 5 };

int 타입의 배열인 intArrayA와 intArrayB가 존재합니다. intArrayA와 intArrayB에 모두 존재하는 요소는 2, 5입니다.

 

배열을 그림으로 나타내면 다음과 같습니다.


오버로드된 두 가지 버전

LINQ의 Intersect() 메서드는 오버로드된 두 가지 버전이 존재합니다.

public static IEnumerable<TSource> Intersect<TSource>(
  this IEnumerable<TSource> first, 
  IEnumerable<TSource> second);

public static IEnumerable<TSource> Intersect<TSource>(
  this IEnumerable<TSource> first, 
  IEnumerable<TSource> second, 
  IEqualityComparer<TSource> comparer);

Intersect() 메서드는 Enumerable 클래스에서 구현되었으며 첫 번째 매개변수를 보면 IEnumerable 인터페이스의 확장 메서드라는 것을 알 수 있습니다. 두 가지 버전의 차이점은 IEqualityComparer 인터페이스 사용 유무입니다.

 

IEqualityComparer 인터페이스가 존재한다는 의미는 Intersect() 메서드를 비교기(Comparer)와 함께 사용할 수 있다는 것이며, 주로 사용자 정의 클래스로 정의된 두 객체를 비교해야 하는 경우 사용됩니다. 


예제 1. int 타입의 List

다음 예제는 int 타입의 List에서 Intersect() 메서드를 사용합니다. 질의 구문에는 Intersect 절이 없으므로 질의 구문 결과에 Intersect() 메서드를 호출합니다.

class Program
{
  static void Main(string[] args)
  {
    List<int> intArrayA = new List<int>() { 0, 2, 4, 5 };
    List<int> intArrayB = new List<int>() { 1, 2, 3, 5 };

    // 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
    List<int> linqQueryResult = (from num in intArrayA
                                 select num).Intersect(intArrayB).ToList();

    // 2. 메서드 구문(Method Syntax)
    List<int> linqMethodResult = intArrayA.Intersect(intArrayB).ToList();

    Console.WriteLine("질의 구문 + 메서드 구문");
    foreach (int num in linqQueryResult)
      Console.Write(num + " ");

    Console.WriteLine("\n메서드 구문");
    foreach (int num in linqMethodResult)
      Console.Write(num + " ");
  }
}

[실행 결과]

질의 구문 + 메서드 구문
2 5
메서드 구문
2 5

예제 2. string 타입의 List

다음 예제는 string 타입의 List에서 Intersect() 메서드를 사용하며 사용 방법은 int 타입에서 설명한 방법과 동일합니다.

class Program
{
  static void Main(string[] args)
  {
    List<string> strArrayA = new List<string>() { "one", "two", "three" };
    List<string> strArrayB = new List<string>() { "One", "Two", "Three" };

    // 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
    List<string> linqQueryResult = (from str in strArrayA
                                    select str).Intersect(strArrayB).ToList();

    // 2. 메서드 구문(Method Syntax)
    List<string> linqMethodResult = strArrayA.Intersect(strArrayB).ToList();

    Console.WriteLine("질의 구문 + 메서드 구문");
    foreach (string str in linqQueryResult)
      Console.Write(str + " ");

    Console.WriteLine("\n메서드 구문");
    foreach (string str in linqMethodResult)
      Console.Write(str + " ");
  }
}

위 예제를 실행하면 출력되는 요소가 존재하지 않습니다. 기본 비교자는 문자열을 비교할 때 대소문자를 구분하므로 "one"과 "One"은 다른 문자열로 취급합니다. 따라서 IEqualityComparer를 매개변수로 사용하는 Intersect() 메서드를 호출합니다.

class Program
{
  static void Main(string[] args)
  {
    List<string> strArrayA = new List<string>() { "one", "two", "three" };
    List<string> strArrayB = new List<string>() { "One", "Two", "Three" };

    // 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
    List<string> linqQueryResult = (from str in strArrayA
                                    select str)
                                    .Intersect(strArrayB, StringComparer.OrdinalIgnoreCase)
                                    .ToList();

    // 2. 메서드 구문(Method Syntax)
    List<string> linqMethodResult = strArrayA
        .Intersect(strArrayB, StringComparer.OrdinalIgnoreCase)
        .ToList();

    Console.WriteLine("질의 구문 + 메서드 구문");
    foreach (string str in linqQueryResult)
      Console.Write(str + " ");

    Console.WriteLine("\n메서드 구문");
    foreach (string str in linqMethodResult)
      Console.Write(str + " ");
  }
}

[실행 결과]

질의 구문 + 메서드 구문
one two three
메서드 구문
one two three

예제 3. 사용자 정의 클래스

Intersect() 메서드는 사용자가 정의한 클래스 타입에서도 사용할 수 있지만 기본 비교자가 다른 방식으로 동작합니다.

 

다음 소스 코드는 Person 클래스를 정의합니다.

public class Person
{
  public string Name { get; set; }
  public int Age { get; set; }
  public override string ToString()
  {
    return "Name: " + Name + ", Age: " + Age;
  }
}

Person 타입의 두 개의 List 객체에서 프로퍼티의 값이 동일한 요소가 존재합니다.

따라서 Insersect() 메서드를 호출하면 한 개의 요소가 반환되어야 합니다.

 

하지만, 다음 예제를 실행하면 예상했던 결과와 달리 출력되는 요소가 존재하지 않습니다.

class Program
{
  static void Main(string[] args)
  {
    List<Person> personA = new List<Person>
    {
      new Person{Name ="Bob", Age = 20},
      new Person{Name ="Tom", Age = 25},
      new Person{Name ="Sam", Age = 30}
    };

    List<Person> personB = new List<Person>
    {
      new Person{Name ="Ella", Age = 15},
      new Person{Name ="Bob",  Age = 20}
    };

    // 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
    List<Person> linqQueryResult = (from person in personA
                                    select person)
                                    .Intersect(personB)
                                    .ToList();

    // 2. 메서드 구문(Method Syntax)
    List<Person> linqMethodResult = personA.Intersect(personB).ToList();

    Console.WriteLine("질의 구문 + 메서드 구문");
    foreach (Person person in linqQueryResult)
      Console.WriteLine(person.ToString());

    Console.WriteLine("\n메서드 구문");
    foreach (Person person in linqMethodResult)
      Console.WriteLine(person.ToString());
  }
}

예제 4. 익명 타입 사용

사용자 정의 클래스 타입의 컬렉션에서 동일한 요소를 추출하는 방법으로 익명 타입을 사용할 수 있습니다. 다음 예제는 Select절을 사용하여 객체의 모든 요소를 익명 타입으로 생성합니다.

class Program
{
  static void Main(string[] args)
  {
    List<Person> personA = new List<Person>
    {
      new Person{Name ="Bob", Age = 20},
      new Person{Name ="Tom", Age = 25},
      new Person{Name ="Sam", Age = 30}
    };

    List<Person> personB = new List<Person>
    {
        new Person{Name ="Ella", Age = 15},
        new Person{Name ="Bob",  Age = 20}
    };

    // 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
    var linqQueryResult = (from person in personA
                           select new { person.Name, person.Age })
                           .Intersect(personB.Select(item => new { item.Name, item.Age }));

    // 2. 메서드 구문(Method Syntax)
    var linqMethodResult = personA.Select(item => new { item.Name, item.Age })
        .Intersect(personB.Select(item => new { item.Name, item.Age }));

    Console.WriteLine("질의 구문 + 메서드 구문");
    foreach (var person in linqQueryResult)
      Console.WriteLine(person.ToString());

    Console.WriteLine("\n메서드 구문");
    foreach (var person in linqMethodResult)
      Console.WriteLine(person.ToString());
  }
}

[실행 결과]

질의 구문 + 메서드 구문
{ Name = Bob, Age = 20 }

메서드 구문
{ Name = Bob, Age = 20 }

예제 5. IEqualityComparer 인터페이스 구현

또 다른 방법으로 IEqualityComparer 인터페이스 구현 클래스를 사용하는 방법이 존재합니다. 다음 예제는 IEqualityComparer 인터페이스 구현 클래스의 인스턴스를 Intersect() 메서드에 전달하여 두 객체의 동일한 요소를 추출합니다.

public class Person
{
  public string Name { get; set; }
  public int Age { get; set; }
  public override string ToString()
  {
    return "Name: " + Name + ", Age: " + Age;
  }
}

public class PersonComparer : IEqualityComparer<Person>
{
  public bool Equals(Person x, Person y)
  {
    if (object.ReferenceEquals(x, y))
    {
      return true;
    }
    if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null))
    {
      return false;
    }
    return x.Name == y.Name && x.Age == y.Age;
  }
  
  public int GetHashCode(Person obj)
  {
    if (obj == null)
    {
      return 0;
    }
    int NameHashCode = obj.Name == null ? 0 : obj.Name.GetHashCode();
    int AgeHashCode = obj.Age.GetHashCode();
    return NameHashCode ^ AgeHashCode;
  }
}

class Program
{
  static void Main(string[] args)
  {
    List<Person> personA = new List<Person>
    {
      new Person{Name ="Bob", Age = 20},
      new Person{Name ="Tom", Age = 25},
      new Person{Name ="Sam", Age = 30}
    };

    List<Person> personB = new List<Person>
    {
      new Person{Name ="Ella", Age = 15},
      new Person{Name ="Bob",  Age = 20}
    };

    PersonComparer personComparer = new PersonComparer();

    // 1. 질의 구문(Query Syntax) + 메서드 구문(Method Syntax)
    List<Person> linqQueryResult = (from person in personA
                                    select person)
                                    .Intersect(personB, personComparer)
                                    .ToList();

    // 2. 메서드 구문(Method Syntax)
    List<Person> linqMethodResult = personA.Intersect(personB, personComparer).ToList();

    Console.WriteLine("질의 구문 + 메서드 구문");
    foreach (Person person in linqQueryResult)
      Console.WriteLine(person.ToString());

    Console.WriteLine("\n메서드 구문");
    foreach (Person person in linqMethodResult)
      Console.WriteLine(person.ToString());
  }
}

[실행 결과]

질의 구문 + 메서드 구문
Name: Bob, Age: 20

메서드 구문
Name: Bob, Age: 20
반응형

댓글