使用linq从两个对象列表创建一个列表


161

我有以下情况

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

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

我需要将2个列表合并到一个新列表List<Person> 中,以防合并记录具有相同的人名,即list2中人的值,更改将是list2的值-list1的值。如果没有重复,则更改为0


2
是否真的需要linq-一个带有linq-ish表达式的不错的foreach也可以做到。
Rashack

1
添加此评论作为问题标题的版本和实际问题不匹配:真正的答案是Mike的回答。大多数其他答案虽然有用,但实际上并不能解决原始海报提出的问题。
约书亚记

Answers:


254

通过使用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扩展Union方法中的第二个参数提供。有关如何编写这种比较器方法的更多信息,请参见http://msdn.microsoft.com/zh-cn/library/system.collections.iequalitycomparer.aspx


10
我看不出这如何回答有关值合并的问题。
瓦格纳·席尔瓦

1
这不会响应,Union将仅包含两个集合中存在的项目,而不包含两个列表之一中存在的任何元素
J4N 2012年

7
@ J4N你也许混淆UnionIntersect
科斯2012年

11
供参考:还有Concat不合并重复项的信息
Kos 2012年

7
您介意编辑此答案以使其实际回答问题吗?我发现一个荒谬的答案是,尽管它没有回答问题,但还是得到了很高的评价,只是因为它回答了标题和基本的Google查询(“ linq合并列表”)。
罗林

78

我注意到这个问题在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 
    }));

尽管在任何一个集合中都有重复的名称时,这不会出错

其他一些答案建议使用联合-这绝对不是可行的方法,因为它只会为您提供一个独特的列表,而无需进行合并。


8
这篇文章实际上回答了问题,并且做得很好。
philu 2015年

3
这应该是公认的答案。从来没有见过这么多人对不回答所提出问题的答案有很高的评价!
Todd Menier

好答案。我可能会对其进行一个小更改,因此Value实际上是list2中的值,因此,如果有重复项,则Change会保持不变:Set Value = p2.Value和Change = p1.Change + p2.Value-p1.Value
拉维德赛

70

为什么不只是使用Concat

Concat是linq的一部分,比做一个 AddRange()

在您的情况下:

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

13
您怎么知道它更有效?
杰里·尼克松

@Jerry Nixon他/她没有测试,但解释似乎合乎逻辑。stackoverflow.com/questions/1337699/…–
Nullius,

9
stackoverflow.com/questions/100196/net-listt-concat-vs-addrange- > 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! 这是我的观点。
J4N

2
而且优点还在于,如果使用实体框架,则可以在SQL端而不是C#端完成此操作。
J4N

4
这样做没有帮助的真正原因是,它实际上并未合并两个列表中存在的任何对象。
Mike Goatly 2015年

15

这是林克

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

12

这样做有几个步骤,假设每个列表不包含重复项,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));

(警告:未经测试。)


2
Append几乎是开箱即用的副本Concat
罗林

@Rawling:出于某种原因,我一直想念它Enumerable.Concat,然后重新实现它。
理查德

2

您需要像完整的外部联接之类的东西。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();

2

以下代码是否可以解决您的问题?我用了一个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();
   }
}

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);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.