C#/LINQ

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

DevStory 2022. 7. 17.

Union 메서드

LINQ의 Union() 메서드는 두 컬렉션에서 중복된 요소를 제거하고 모든 요소를 하나로 결합합니다. 예를 들어 다음 소스 코드를 봅시다.

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

int 타입의 배열인 intArrayA와 intArrayB가 존재합니다. intArray와 intArrayB에 중복된 요소는 2, 5입니다. 따라서, 두 컬렉션을 Union() 메서드를 사용하여 하나로 결합하면 다음과 같은 결과가 나와야 합니다.

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

오버로드된 두 가지 버전

LINQ의 Union() 메서드 사용 예제를 알아보기 전에 오버로드된 두 가지 버전에 대해 알아봅시다.

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

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

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

 

IEqualityComparer 인터페이스는 Union() 메서드를 비교기(Comparer)와 함께 사용할 수 있다는 것이며, 주로 사용자 정의 클래스로 정의된 두 객체에서 어떠한 기준(모든 프로퍼티 또는 특정 프로퍼티)으로 비교할 것인지 정의하기 위해 사용됩니다.


예제 1. int 타입의 List

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

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).Union(intArrayB).ToList();

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

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

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

[실행 결과]

질의 구문 + 메서드 구문
0 2 4 5 1 3
메서드 구문
0 2 4 5 1 3

예제 2. string 타입의 List

다음 예제는 string 타입의 List에서 Union() 메서드를 사용합니다.

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).Union(strArrayB).ToList();

    // 2. 메서드 구문(Method Syntax)
    List<string> linqMethodResult = strArrayA.Union(strArrayB).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
메서드 구문
one two three One Two Three

위 예제를 실행하면 모는 요소가 출력됩니다. Union() 메서드의 기본 비교자는 문자열을 비교할 때 대소문자를 구분하지 않기 때문입니다.

 

따라서, 대소문자를 구분하기 위해서는 IEqualityComparer를 매개변수로 사용하는 Union() 메서드를 사용합니다.

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)
                                    .Union(strArrayB, StringComparer.OrdinalIgnoreCase)
                                    .ToList();

    // 2. 메서드 구문(Method Syntax)
    List<string> linqMethodResult = strArrayA
        .Union(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. 사용자 정의 클래스

Union() 메서드는 사용자가 정의한 클래스 타입에서도 사용할 수 있습니다.

 

다음 소스 코드는 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 객체가 존재하며 프로퍼티의 값이 동일한 요소가 존재합니다.

Union() 메서드를 사용하여 Name 프로퍼티만 추출하는 경우 "Bob"은 두 개 이상 존재하면 안됩니다. 따라서, select절을 사용하여 Name 프로퍼티만 추출 후 Union() 메서드를 사용합니다.

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<string> linqQueryResult = (from person in personA
                                    select person.Name)
                                    .Union(personB.Select(person => person.Name))
                                    .ToList();

    // 2. 메서드 구문(Method Syntax)
    List<string> linqMethodResult = personA.Select(person => person.Name)
        .Union(personB.Select(person => person.Name))
        .ToList();

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

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

[실행 결과]

질의 구문 + 메서드 구문
Bob
Tom
Sam
Ella

메서드 구문
Bob
Tom
Sam
Ella

예제 4. 익명 타입 사용

이번에는 특정 프로퍼티가 아닌 모든 프로퍼티를 비교합니다. personA와 personB에 Name, Age 프로퍼티가 동일한 요소가 존재하므로 중복된 요소는 한 개만 추출되어야 합니다.

 

다음 예제는 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}
    };

    PersonComparer personComparer = new PersonComparer();

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

    // 2. 메서드 구문(Method Syntax)
    var linqMethodResult = personA.Select(item => new { item.Name, item.Age })
        .Union(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 = Tom, Age = 25 }
{ Name = Sam, Age = 30 }
{ Name = Ella, Age = 15 }

메서드 구문
{ Name = Bob, Age = 20 }
{ Name = Tom, Age = 25 }
{ Name = Sam, Age = 30 }
{ Name = Ella, Age = 15 }

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

또 다른 방법으로 IEqualityComparer 인터페이스 구현 클래스를 사용합니다. 다음 예제는 IEqualityComparer 인터페이스 구현 클래스의 인스턴스를 Union() 메서드에 전달합니다.

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)
                                    .Union(personB, personComparer)
                                    .ToList();

    // 2. 메서드 구문(Method Syntax)
    List<Person> linqMethodResult = personA.Union(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: Tom, Age: 25
Name: Sam, Age: 30
Name: Ella, Age: 15

메서드 구문
Name: Bob, Age: 20
Name: Tom, Age: 25
Name: Sam, Age: 30
Name: Ella, Age: 15
반응형

댓글