linq를 사용하여 두 개의 객체 목록에서 목록 만들기


161

다음과 같은 상황이 있습니다

class Person
{
    string Name;
    int Value;
    int Change;
}

List<Person> list1;
List<Person> list2;

List<Person> 결합 레코드가 같은 이름을 가진 사람, list2에있는 사람의 값, 변경이 list2의 값-list1의 값이 될 경우를 대비 하여 두 목록을 새 목록으로 결합해야합니다. 중복이 없으면 변경은 0입니다


2
linq가 정말로 필요합니까-약간의 linq-ish 표현이있는 멋진 foreach도 가능합니다.
Rashack

1
이 의견을 질문 제목의 버전으로 추가하는데 실제 질문과 일치하지 않습니다. 이에 대한 실제 답변은 Mike의 답변입니다 . 대부분의 다른 답변은 유용하지만 원래 포스터에서 제시 한 문제를 실제로 해결하지는 않습니다.
Joshua

답변:


254

Linq 확장 방법 Union을 사용하여 쉽게 수행 할 수 있습니다. 예를 들면 다음과 같습니다.

var mergedList = list1.Union(list2).ToList();

그러면 두 목록이 병합되고 이중 목록이 제거 된 목록이 반환됩니다. 예제와 같이 Union 확장 메서드에서 비교자를 지정하지 않으면 Person 클래스의 기본 Equals 및 GetHashCode 메서드가 사용됩니다. 예를 들어 Name 속성을 비교하여 사람을 비교하려는 경우 이러한 방법을 재정 의하여 직접 비교를 수행해야합니다. 이를 위해 다음 코드 샘플을 확인하십시오. 이 코드를 Person 클래스에 추가해야합니다.

/// <summary>
/// Checks if the provided object is equal to the current Person
/// </summary>
/// <param name="obj">Object to compare to the current Person</param>
/// <returns>True if equal, false if not</returns>
public override bool Equals(object obj)
{        
    // Try to cast the object to compare to to be a Person
    var person = obj as Person;

    return Equals(person);
}

/// <summary>
/// Returns an identifier for this instance
/// </summary>
public override int GetHashCode()
{
    return Name.GetHashCode();
}

/// <summary>
/// Checks if the provided Person is equal to the current Person
/// </summary>
/// <param name="personToCompareTo">Person to compare to the current person</param>
/// <returns>True if equal, false if not</returns>
public bool Equals(Person personToCompareTo)
{
    // Check if person is being compared to a non person. In that case always return false.
    if (personToCompareTo == null) return false;

    // If the person to compare to does not have a Name assigned yet, we can't define if it's the same. Return false.
    if (string.IsNullOrEmpty(personToCompareTo.Name) return false;

    // Check if both person objects contain the same Name. In that case they're assumed equal.
    return Name.Equals(personToCompareTo.Name);
}

Person 클래스의 기본 Equals 메소드가 항상 Name을 사용하여 두 객체를 비교하지 않도록 설정하려는 경우 IEqualityComparer 인터페이스를 사용하는 비교기 클래스를 작성할 수도 있습니다. 그런 다음 Linq 확장 유니온 메소드에서이 비교자를 두 번째 매개 변수로 제공 할 수 있습니다. 이러한 비교기 메소드를 작성하는 방법에 대한 자세한 정보는 http://msdn.microsoft.com/en-us/library/system.collections.iequalitycomparer.aspx 를 참조 하십시오.


10
이것이 값 병합에 대한 질문에 어떻게 대답하는지 알 수 없습니다.
바그너 다 실바

1
이에 응답하지 않습니다. Union은 두 세트에있는 항목 만 포함하고 두 목록 중 하나에있는 요소는 포함하지 않습니다.
J4N

7
J4N @ 당신은 아마도 혼동 Union으로 Intersect?
Kos

11
참고로 : Concat중복을 병합하지 않는 것도 있습니다
Kos

7
이 답변을 편집하여 실제로 질문에 답변 하시겠습니까? 제목과 기본 Google 검색어 ( 'linq merge list')에 답변하기 때문에 질문에 답변하지 않는다는 사실에도 불구하고 답변이 너무 많이 투표되었다는 것은 우스운 일입니다.
롤링

78

나는이 질문이 2 년 후에 답변 된 것으로 표시되지 않았다는 것을 알았습니다. 가장 가까운 대답은 Richards라고 생각하지만 다음과 같이 상당히 단순화 될 수 있습니다.

list1.Concat(list2)
    .ToLookup(p => p.Name)
    .Select(g => g.Aggregate((p1, p2) => new Person 
    {
        Name = p1.Name,
        Value = p1.Value, 
        Change = p2.Value - p1.Value 
    }));

둘 중 하나에 중복 된 이름이있는 경우에는 오류가 발생 하지 않습니다 .

다른 대답은 노조 사용을 제안했습니다. 결합을하지 않고 당신에게 별개의 목록을 얻을 수 있기 때문에 분명히 갈 길이 아닙니다.


8
이 게시물은 실제로 질문에 대답하고 잘 수행합니다.
philu

3
이것이 정답입니다. 질문에 대한 답변이없는 답변에 대한 많은 공감대가있는 질문을 본 적이 없습니다!
Todd Menier

좋은 대답입니다. 하나의 작은 변경을 할 수 있으므로 Value는 실제로 list2의 값이므로 중복이있는 경우 변경이 유지됩니다 .Set Value = p2.Value 및 Change = p1.Change + p2.Value-p1.Value
Ravi Desai

70

왜 사용하지 Concat않습니까?

Concat은 linq의 일부이며 AddRange()

귀하의 경우 :

List<Person> list1 = ...
List<Person> list2 = ...
List<Person> total = list1.Concat(list2);

13
그것이 더 효율적인지 어떻게 알 수 있습니까?
Jerry Nixon

@ Jerry Nixon 테스트하지 않았지만 설명은 논리적 인 것 같습니다. stackoverflow.com/questions/1337699/…
Nullius

9
stackoverflow.com/questions/100196/net-listt-concat-vs-addrange- > Greg의 의견 : Actually, due to deferred execution, using Concat would likely be faster because it avoids object allocation - Concat doesn't copy anything, it just creates links between the lists so when enumerating and you reach the end of one it transparently takes you to the start of the next! 이것은 내 요점입니다.
J4N

2
또한 Entity Framework를 사용하는 경우 C # 대신 SQL 쪽에서 수행 할 수 있다는 장점도 있습니다.
J4N

4
이것이 도움이되지 않는 진짜 이유는 실제로 두 목록에있는 객체를 병합하지 않기 때문입니다.
Mike Goatly

15

이것은 Linq입니다

var mergedList = list1.Union(list2).ToList();

이것은 Normaly입니다 (AddRange).

var mergedList=new List<Person>();
mergeList.AddRange(list1);
mergeList.AddRange(list2);

이것은 Normaly입니다 (Foreach).

var mergedList=new List<Person>();

foreach(var item in list1)
{
    mergedList.Add(item);
}
foreach(var item in list2)
{
     mergedList.Add(item);
}

이것은 Normaly입니다 (Foreach-Dublice).

var mergedList=new List<Person>();

foreach(var item in list1)
{
    mergedList.Add(item);
}
foreach(var item in list2)
{
   if(!mergedList.Contains(item))
   {
     mergedList.Add(item);
   }
}

12

각 목록에 중복 항목이없고 이름이 고유 식별자이고 목록이 정렬되지 않았다고 가정하면이 작업에는 몇 가지가 있습니다.

먼저 추가 확장 메소드를 작성하여 단일 목록을 가져 오십시오.

static class Ext {
  public static IEnumerable<T> Append(this IEnumerable<T> source,
                                      IEnumerable<T> second) {
    foreach (T t in source) { yield return t; }
    foreach (T t in second) { yield return t; }
  }
}

따라서 단일 목록을 얻을 수 있습니다.

var oneList = list1.Append(list2);

그런 다음 이름으로 그룹화하십시오.

var grouped = oneList.Group(p => p.Name);

그런 다음 도우미로 각 그룹을 처리하여 한 번에 하나의 그룹을 처리 할 수 ​​있습니다.

public Person MergePersonGroup(IGrouping<string, Person> pGroup) {
  var l = pGroup.ToList(); // Avoid multiple enumeration.
  var first = l.First();
  var result = new Person {
    Name = first.Name,
    Value = first.Value
  };
  if (l.Count() == 1) {
    return result;
  } else if (l.Count() == 2) {
    result.Change = first.Value - l.Last().Value;
    return result;
  } else {
    throw new ApplicationException("Too many " + result.Name);
  }
}

다음의 각 요소에 적용 할 수 있습니다 grouped.

var finalResult = grouped.Select(g => MergePersonGroup(g));

(경고 : 테스트되지 않았습니다.)


2
귀하 Append는 기본 제공품의 거의 동일한 사본입니다 Concat.
Rawling

@Rawling : 어떤 이유로 든 누락 Enumerable.Concat되어 다시 구현했습니다.
Richard

2

완전한 외부 조인과 같은 것이 필요합니다. System.Linq.Enumerable에는 전체 외부 조인을 구현하는 메서드가 없으므로 직접 수행해야합니다.

var dict1 = list1.ToDictionary(l1 => l1.Name);
var dict2 = list2.ToDictionary(l2 => l2.Name);
    //get the full list of names.
var names = dict1.Keys.Union(dict2.Keys).ToList();
    //produce results
var result = names
.Select( name =>
{
  Person p1 = dict1.ContainsKey(name) ? dict1[name] : null;
  Person p2 = dict2.ContainsKey(name) ? dict2[name] : null;
      //left only
  if (p2 == null)
  {
    p1.Change = 0;
    return p1;
  }
      //right only
  if (p1 == null)
  {
    p2.Change = 0;
    return p2;
  }
      //both
  p2.Change = p2.Value - p1.Value;
  return p2;
}).ToList();

2

다음 코드가 문제에 적용됩니까? 나는리스트를 결합하기 위해 내부에 약간의 linq가있는 foreach를 사용했으며 사람들이 이름이 일치하면 사람들이 같다고 가정했으며 실행시 예상 값을 인쇄하는 것으로 보입니다. Resharper는 foreach를 linq로 변환하기위한 제안을 제공하지 않으므로 아마도이 방법으로 얻을 수있을 것입니다.

public class Person
{
   public string Name { get; set; }
   public int Value { get; set; }
   public int Change { get; set; }

   public Person(string name, int value)
   {
      Name = name;
      Value = value;
      Change = 0;
   }
}


class Program
{
   static void Main(string[] args)
   {
      List<Person> list1 = new List<Person>
                              {
                                 new Person("a", 1),
                                 new Person("b", 2),
                                 new Person("c", 3),
                                 new Person("d", 4)
                              };
      List<Person> list2 = new List<Person>
                              {
                                 new Person("a", 4),
                                 new Person("b", 5),
                                 new Person("e", 6),
                                 new Person("f", 7)
                              };

      List<Person> list3 = list2.ToList();

      foreach (var person in list1)
      {
         var existingPerson = list3.FirstOrDefault(x => x.Name == person.Name);
         if (existingPerson != null)
         {
            existingPerson.Change = existingPerson.Value - person.Value;
         }
         else
         {
            list3.Add(person);
         }
      }

      foreach (var person in list3)
      {
         Console.WriteLine("{0} {1} {2} ", person.Name,person.Value,person.Change);
      }
      Console.Read();
   }
}

1
public void Linq95()
{
    List<Customer> customers = GetCustomerList();
    List<Product> products = GetProductList();

    var customerNames =
        from c in customers
        select c.CompanyName;
    var productNames =
        from p in products
        select p.ProductName;

    var allNames = customerNames.Concat(productNames);

    Console.WriteLine("Customer and product names:");
    foreach (var n in allNames)
    {
        Console.WriteLine(n);
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.