我有以下情况
class Person
{
string Name;
int Value;
int Change;
}
List<Person> list1;
List<Person> list2;
我需要将2个列表合并到一个新列表List<Person>
中,以防合并记录具有相同的人名,即list2中人的值,更改将是list2的值-list1的值。如果没有重复,则更改为0
我有以下情况
class Person
{
string Name;
int Value;
int Change;
}
List<Person> list1;
List<Person> list2;
我需要将2个列表合并到一个新列表List<Person>
中,以防合并记录具有相同的人名,即list2中人的值,更改将是list2的值-list1的值。如果没有重复,则更改为0
Answers:
通过使用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
Union
与Intersect
?
Concat
不合并重复项的信息
我注意到这个问题在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
}));
尽管在任何一个集合中都有重复的名称时,这不会出错。
其他一些答案建议使用联合-这绝对不是可行的方法,因为它只会为您提供一个独特的列表,而无需进行合并。
为什么不只是使用Concat
?
Concat是linq的一部分,比做一个 AddRange()
在您的情况下:
List<Person> list1 = ...
List<Person> list2 = ...
List<Person> total = list1.Concat(list2);
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!
这是我的观点。
这是林克
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);
}
}
这样做有几个步骤,假设每个列表不包含重复项,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));
(警告:未经测试。)
Append
几乎是开箱即用的副本Concat
。
Enumerable.Concat
,然后重新实现它。
您需要像完整的外部联接之类的东西。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();
以下代码是否可以解决您的问题?我用了一个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();
}
}
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);
}
}