Programming

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

procodes 2020. 6. 9. 22:32
반응형

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

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


You need something like a full outer join. System.Linq.Enumerable has no method that implements a full outer join, so we have to do it ourselves.

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

Does the following code work for your problem? I've used a foreach with a bit of linq inside to do the combining of lists and assumed that people are equal if their names match, and it seems to print the expected values out when run. Resharper doesn't offer any suggestions to convert the foreach into linq so this is probably as good as it'll get doing it this way.

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

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

참고URL : https://stackoverflow.com/questions/720609/create-a-list-from-two-object-lists-with-linq

반응형