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

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

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

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

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



답변

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 를 참조 하십시오.


답변

나는이 질문이 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
    }));

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

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


답변

왜 사용하지 Concat않습니까?

Concat은 linq의 일부이며 AddRange()

귀하의 경우 :

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


답변

이것은 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);
   }
}


답변

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

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

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

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


답변

완전한 외부 조인과 같은 것이 필요합니다. 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();


답변

다음 코드가 문제에 적용됩니까? 나는리스트를 결합하기 위해 내부에 약간의 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();
   }
}