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

我有以下情况

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


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

我需要合并成一个新的 List<Person>的两个列表 如果是同一个人,那么组合记录中的这个人的名字,list2中的这个人的值,就是 list2的值,也就是 list1的值。 如果没有重复,则更改为0

306326 次浏览

假设每个列表不包含重复的内容,那么有几个部分可以做到这一点,Name 是一个唯一标识符,而且两个列表都不排序。

首先创建追加扩展方法以获取单个列表:

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

(警告: 未经测试)

你需要一个完整的外部连接。系统。Linq.枚举数没有实现完整外部联接的方法,因此我们必须自己完成。

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

这可以通过使用 Linq 扩展方法 Union 轻松实现,例如:

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

这将返回一个 List,其中合并了两个列表并删除了双精度类型。如果不像我的示例那样在 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/en-us/library/system.collections.iequalitycomparer.aspx上找到

我注意到这个问题在两年后没有被标记为答案——我认为最接近的答案是理查兹,但是它可以被简化到这样的程度:

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

尽管在两个集合中都有重复名称的情况下,这不会是 错误

其他一些答案建议使用联合——这绝对不是一种方法,因为它只会给你一个独特的列表,而不会进行联合。

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

为什么不用 Concat呢?

Concat 是 linq 的一部分,比执行 AddRange()更有效率

以你为例:

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

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

我是 Linq

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

这是正常值(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);
}
}