Linq ile iki nesne listesinden bir liste oluşturun


161

Bende şu durum var

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

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

Ben List<Person> birleştirmek kayıt o adı, liste2 kişinin değeri, değişiklik list2 değeri - liste1 değeri olurdu aynı kişi olması durumunda yeni bir 2 liste birleştirmek gerekiyor . Kopya yoksa değişiklik 0 olur


2
Linq gerçekten gerekli - biraz linq-ish ifadeleri ile güzel bir foreach de yapabilirdi.
Rashack

1
Bu yorumu, soru başlığının bir sürümü olarak eklemek ve asıl soru eşleşmiyor: Bunun gerçek cevabı Mike'ın bu cevabı . Diğer cevapların çoğu, yararlı olsa da, orijinal poster tarafından sunulan sorunu gerçekten çözmez.
Joshua

Yanıtlar:


254

Bu, Linq genişletme yöntemi Birliği kullanılarak kolayca yapılabilir. Örneğin:

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

Bu, iki listenin birleştirildiği ve çiftlerin kaldırıldığı bir Liste döndürür. Örneğimdeki gibi Union uzantısı yönteminde bir karşılaştırıcı belirtmezseniz, Person sınıfınızdaki varsayılan Eşittir ve GetHashCode yöntemlerini kullanır. Örneğin, kişileri Name özelliklerini karşılaştırarak karşılaştırmak isterseniz, karşılaştırmayı kendiniz gerçekleştirmek için bu yöntemleri geçersiz kılmalısınız. Bunu yapmak için aşağıdaki kod örneğini kontrol edin. Bu kodu Person sınıfınıza eklemelisiniz.

/// <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 sınıfınızın varsayılan Eşit yöntemini her zaman iki nesneyi karşılaştırmak için Ad'ı kullanacak şekilde ayarlamak istemiyorsanız, IEqualityComparer arabirimini kullanan bir karşılaştırma sınıfı da yazabilirsiniz. Daha sonra bu karşılaştırıcıyı Linq extension Union yönteminde ikinci parametre olarak sağlayabilirsiniz. Böyle bir karşılaştırma yönteminin nasıl yazılacağı hakkında daha fazla bilgi http://msdn.microsoft.com/en-us/library/system.collections.iequalitycomparer.aspx adresinde bulunabilir.


10
Bunun değerlerin birleştirilmesiyle ilgili soruyu nasıl cevapladığını anlamıyorum.
Wagner da Silva

1
Bu yanıt vermiyor, Birlik yalnızca iki kümede bulunan öğeleri
içerecek

7
J4N @ belki karıştırıyorsun Unionile Intersect?
Kos

11
Referans için: Concatkopyaları birleştirmeyen de var
Kos

7
Bu yanıtı gerçekten soruyu cevaplayacak şekilde düzenlemeyi düşünür müsünüz? Sadece başlığı ve temel bir Google sorgusunu ("linq merge listeler") yanıtladığı için soruyu cevaplamamasına rağmen bir cevabın çok yüksek oy kullanmasını saçma buluyorum.
Rawling

78

Bu sorunun 2 yıl sonra cevap olarak işaretlenmediğini fark ettim - en yakın cevabın Richards olduğunu düşünüyorum, ancak bunun için çok basitleştirilebilir:

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

Her iki kümede de yinelenen adların olması durumunda bu hata olmaz .

Diğer bazı cevaplar sendikalamayı önerdi - bu kesinlikle bir araya gelmeden sadece ayrı bir liste alacağı için gitmenin yolu değil.


8
Bu yazı aslında soruyu cevaplıyor ve iyi yapıyor.
philu

3
Bu kabul edilen cevap olmalı. Asla sorulan soruya cevap vermeyen cevaplar için bu kadar çok upvotes ile bir soru görmedim!
Todd Menier

Güzel cevap. Bunun için küçük bir değişiklik yapabilirim, bu yüzden Değer aslında list2'deki değerdir ve böylece kopyalarınız varsa Değişiklik devam eder: Set Value = p2.Value and Change = p1.Change + p2.Value - p1.Value
Ravi Desai

70

Neden sadece kullanmıyorsun? Concat ?

Concat, linq'in bir parçasıdır ve AddRange()

Senin durumunda:

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

13
Daha verimli olduğunu nasıl anlarsınız?
Jerry Nixon

@Jerry Nixon Test etmedi, ancak açıklama mantıklı görünüyor. stackoverflow.com/questions/1337699/…
Nullius

9
stackoverflow.com/questions/100196/net-listt-concat-vs-addrange -> Greg'in yorumu: 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! Bu benim açımdan.
J4N

2
Ve avantajı, Entity Framework kullanıyorsanız, bunun C # tarafı yerine SQL tarafında yapılabilmesidir.
J4N

4
Bunun yardımcı olmamasının asıl nedeni, her iki listede bulunan nesnelerin hiçbirini gerçekten birleştirmemesidir.
Mike Goatly

15

Bu Linq

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

Bu Normalde (AddRange)

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

Bu Normalde (Foreach)

var mergedList=new List<Person>();

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

Bu Normalde (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

Bunu yapmak için, her listenin kopya içermediğini, Ad'ın benzersiz bir tanımlayıcı olduğunu ve her iki listenin de sipariş edilmediğini varsayarak bunu yapmak için birkaç parça vardır.

Tek bir liste almak için önce bir ekleme uzantısı yöntemi oluşturun:

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

Böylece tek bir liste alabilirsiniz:

var oneList = list1.Append(list2);

Sonra isimlendirin

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

Daha sonra her bir grubu bir seferde bir grubu işlemek için bir yardımcı ile işleyebilir

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

Hangi her öğeye uygulanabilir grouped:

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

(Uyarı: denenmemiş.)


2
Kullanıma hazır Appendkutusunun neredeyse tam bir kopyası Concat.
Rawling

@Rawling: Bir nedenden dolayı eksik kaldım Enumerable.Concatve böylece tekrar uyguladım.
Richard

2

Tam bir dış birleşim gibi bir şeye ihtiyacınız var. System.Linq.Enumerable'ın tam bir dış birleşimi uygulayan bir yöntemi yoktur, bu yüzden bunu kendimiz yapmak zorundayız.

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

Sorununuz için aşağıdaki kod çalışıyor mu? Listeleri birleştirmek için içeride linq ile bir foreach kullandım ve isimleri eşleşirse insanların eşit olduğunu varsaydım ve çalıştırıldığında beklenen değerleri yazdırıyor gibi görünüyor. Resharper, foreach'i linq'e dönüştürmek için herhangi bir öneri sunmaz, bu yüzden muhtemelen bu şekilde alacağı kadar iyidir.

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);
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.