Tạo một danh sách từ hai danh sách đối tượng với linq


161

Tôi có tình huống sau

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

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

Tôi cần kết hợp 2 danh sách thành một danh sách mới List<Person> trong trường hợp đó là cùng một người, bản ghi kết hợp sẽ có tên đó, giá trị của người trong danh sách 2 , thay đổi sẽ là giá trị của list2 - giá trị của list1. Thay đổi là 0 nếu không trùng lặp


2
Là linq thực sự cần thiết - một foreach tốt đẹp với một chút biểu thức linq-ish cũng có thể làm.
Rashack

1
Thêm nhận xét này dưới dạng phiên bản của tiêu đề của câu hỏi và câu hỏi thực tế không khớp: câu trả lời thực sự cho câu hỏi này là câu trả lời từ Mike . Hầu hết các câu trả lời khác, trong khi hữu ích, không thực sự giải quyết vấn đề được trình bày bởi người đăng ban đầu.
Joshua

Câu trả lời:


254

Điều này có thể dễ dàng được thực hiện bằng cách sử dụng phương pháp mở rộng Linq Union. Ví dụ:

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

Điều này sẽ trả về một Danh sách trong đó hai danh sách được hợp nhất và nhân đôi được loại bỏ. Nếu bạn không chỉ định một trình so sánh trong phương thức mở rộng Liên minh như trong ví dụ của tôi, nó sẽ sử dụng các phương thức Equals và GetHashCode mặc định trong lớp Person của bạn. Ví dụ, nếu bạn muốn so sánh người bằng cách so sánh thuộc tính Tên của họ, bạn phải ghi đè các phương thức này để tự thực hiện so sánh. Kiểm tra mẫu mã sau đây để thực hiện điều đó. Bạn phải thêm mã này vào lớp Người của bạn.

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

Nếu bạn không muốn đặt phương thức Equals mặc định của lớp Person của mình để luôn sử dụng Tên để so sánh hai đối tượng, bạn cũng có thể viết một lớp so sánh sử dụng giao diện IEqualityComparer. Sau đó, bạn có thể cung cấp bộ so sánh này làm tham số thứ hai trong phương thức Liên kết mở rộng Linq. Thông tin thêm về cách viết phương pháp so sánh như vậy có thể được tìm thấy trên http://msdn.microsoft.com/en-us/l Library / system.collections.iequalitycomparer.aspx


10
Tôi không thấy cách này trả lời câu hỏi về việc hợp nhất các giá trị.
Wagner da Silva

1
Điều này không phản hồi, Union sẽ chỉ chứa các mục có trong hai bộ, không có bất kỳ yếu tố nào có trong một trong hai danh sách
J4N

7
@ J4N có lẽ bạn đang nhầm lẫn Unionvới Intersect?
Kos

11
Để tham khảo: cũng Concatkhông hợp nhất các bản sao
Kos

7
Bạn có phiền khi chỉnh sửa câu trả lời này để nó thực sự trả lời câu hỏi không? Tôi thấy thật nực cười khi một câu trả lời được bình chọn rất cao mặc dù thực tế nó không trả lời câu hỏi, chỉ vì nó trả lời tiêu đề và một truy vấn cơ bản của Google ("danh sách hợp nhất linq").
Rawling

78

Tôi nhận thấy rằng câu hỏi này không được đánh dấu là đã trả lời sau 2 năm - Tôi nghĩ rằng câu trả lời gần nhất là Richards, nhưng nó có thể được đơn giản hóa khá nhiều cho việc này:

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

Mặc dù điều này sẽ không có lỗi trong trường hợp bạn có tên trùng lặp trong cả hai.

Một số câu trả lời khác đã đề xuất sử dụng kết hợp - đây chắc chắn không phải là cách để đi vì nó sẽ chỉ giúp bạn có một danh sách riêng biệt, mà không cần thực hiện kết hợp.


8
Bài đăng này thực sự trả lời câu hỏi, và làm nó tốt.
philu

3
Đây phải là câu trả lời được chấp nhận. Chưa bao giờ thấy một câu hỏi với rất nhiều câu trả lời cho câu trả lời mà không trả lời câu hỏi được hỏi!
Todd Menier

Câu trả lời tốt đẹp. Tôi có thể thực hiện một thay đổi nhỏ cho nó, vì vậy Giá trị thực sự là giá trị từ list2 và do đó Thay đổi sẽ tiếp tục nếu bạn có các bản sao: Đặt Giá trị = p2. Giá trị và Thay đổi = p1.Thay đổi + p2. Giá trị - p1. Giá trị
Ravi Desai

70

Tại sao bạn không sử dụng Concat?

Concat là một phần của linq và hiệu quả hơn so với thực hiện AddRange()

trong trường hợp của bạn:

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

13
Làm thế nào để bạn biết nó hiệu quả hơn?
Jerry Nixon

@Jerry Nixon Anh ấy / cô ấy đã không kiểm tra nó, nhưng lời giải thích có vẻ hợp lý. stackoverflow.com/questions/1337699/
Mạnh

9
stackoverflow.com/questions/100196/net-listt-concat-vs-addrange -> Nhận xét của 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! Đây là quan điểm của tôi.
J4N

2
Và lợi thế cũng là nếu bạn sử dụng Entity Framework, điều này có thể được thực hiện ở phía SQL thay vì phía C #.
J4N

4
Lý do thực sự điều này không có ích là vì nó không thực sự hợp nhất bất kỳ đối tượng nào có trong cả hai danh sách.
Mike Dê

15

Đây là Linq

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

Đây là Normaly (AddRange)

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

Đây là Normaly (Foreach)

var mergedList=new List<Person>();

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

Đây là 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

Có một vài phần để làm điều này, giả sử mỗi danh sách không chứa các mục trùng lặp, Tên là một định danh duy nhất và không có danh sách nào được sắp xếp.

Đầu tiên tạo một phương thức mở rộng để thêm một danh sách:

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

Do đó có thể nhận được một danh sách duy nhất:

var oneList = list1.Append(list2);

Sau đó nhóm về tên

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

Sau đó có thể xử lý mỗi nhóm với một người trợ giúp để xử lý một nhóm tại một thời điểm

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

Mà có thể được áp dụng cho từng yếu tố của grouped:

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

(Cảnh báo: chưa được kiểm tra.)


2
Của bạn Appendlà một bản sao gần như chính xác của bên ngoài hộp Concat.
Rawling

@Rawling: Vì một số lý do, tôi đã mất tích Enumerable.Concatvà do đó thực hiện lại nó.
Richard

2

Bạn cần một cái gì đó như một tham gia bên ngoài đầy đủ. System.Linq.Enumerable không có phương thức thực hiện tham gia bên ngoài đầy đủ, vì vậy chúng tôi phải tự làm điều đó.

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

Liệu các mã sau đây làm việc cho vấn đề của bạn? Tôi đã sử dụng một foreach với một chút linq bên trong để thực hiện kết hợp các danh sách và cho rằng mọi người bằng nhau nếu tên của họ trùng khớp và dường như in ra các giá trị dự kiến ​​khi chạy. Resharper không đưa ra bất kỳ đề xuất nào để chuyển đổi foreach thành linq vì vậy điều này có thể tốt như nó sẽ được thực hiện theo cách này.

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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.