Создайте список из двух списков объектов с помощью linq

У меня следующая ситуация.

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

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

Мне нужно объединить 2 списка в новый List<Person> в случае, если это одно и то же лицо, запись объединения будет иметь это имя, значение человека в списке2, изменение будет значением list2 - значением list1. Изменение равно 0, если нет дубликата

Ответ 1

Это можно легко сделать с помощью метода расширения Linq Union. Например:

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

Это вернет список, в котором два списка объединены, и удваивается. Если вы не укажете компаратор в методе расширения Union, как в моем примере, он будет использовать методы Equals и GetHashCode по умолчанию в вашем классе Person. Если вы, например, хотите сравнить людей, сравнивая их свойство 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 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);
}

Если вы не хотите, чтобы метод Equals по умолчанию вашего класса Person всегда использовал имя для сравнения двух объектов, вы также можете написать класс сравнения, который использует интерфейс IEqualityComparer. Затем вы можете предоставить этот компаратор как второй параметр в методе расширения Linq. Более подробную информацию о том, как написать такой метод сравнения, можно найти на http://msdn.microsoft.com/en-us/library/system.collections.iequalitycomparer.aspx

Ответ 2

Почему вы не просто используете Concat?

Concat является частью linq и более эффективным, чем выполнение AddRange()

в вашем случае:

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

Ответ 3

Я заметил, что этот вопрос не был отмечен как ответ через 2 года - я думаю, что ближайший ответ - Ричардс, но его можно довольно упростить:

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

Хотя это не будет ошибкой в ​​случае, когда у вас есть дубликаты имен в любом наборе.

Некоторые другие ответы предложили использовать unioning - это определенно не путь, потому что он будет получать только отдельный список, не комбинируя.

Ответ 4

Есть несколько частей для этого, предполагая, что в каждом списке нет дубликатов, Name - уникальный идентификатор, и ни один список не упорядочен.

Сначала создайте метод расширения append для получения одного списка:

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

(Предупреждение: непроверено.)

Ответ 5

Это Linq

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

Это Normaly (AddRange)

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

Это нормальный (Foreach)

var mergedList=new List<Person>();

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

Это нормальный (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);
   }
}

Ответ 6

Вам нужно что-то вроде полного внешнего соединения. 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();

Ответ 7

Использует ли следующий код для вашей проблемы? Я использовал foreach с немного linq внутри, чтобы сделать объединение списков и предположил, что люди равны, если их имена совпадают, и, кажется, печатает ожидаемые значения при запуске. 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();
   }
}

Ответ 8

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