根据属性将两个列表合并为一个

pza*_*zaj 5 .net c# linq .net-4.5

我想问一下,是否有一种优雅而有效的方法将两个MyClass列表合并为一个?

MyClass看起来像这样:

  • ID: int
  • 名称: string
  • EXTID: int?

并且列表从不同的源填充,列表中的对象共享ID,所以它看起来像这样:

MyClass instance from List1
ID = someInt
Name = someString
ExtID = null
Run Code Online (Sandbox Code Playgroud)

来自List2的MyClass实例

ID = someInt (same as List1)
Name = someString (same as List1)
ExtID = someInt
Run Code Online (Sandbox Code Playgroud)

我基本上需要的是将这两个列表组合在一起,因此结果是一个包含以下内容的列表:

ID = someInt (from List1)
Name = someString (from List1)
ExtID = someInt (null if no corresponding item - based on ID - on List2)
Run Code Online (Sandbox Code Playgroud)

我知道我可以简单地使用foreach循环来做到这一点,但是我想知道是否有更优雅和可能更喜欢(由于性能,可读性)方法?

mik*_*kus 3

有很多方法取决于优先级,例如。联合+查找:

//this will create a key value pairs: id -> matching instances
var idMap = list1.Union(list2).ToLookup(myClass => myClass.ID);
//now just select for each ID the instance you want, ex. with some value
var mergedInstances = idMap.Select(row => 
      row.FirstOrDefault(myClass => myClass.ExtId.HasValue) ?? row.First());
Run Code Online (Sandbox Code Playgroud)

上面的好处是,它可以处理任何数量的任何列表,即使它们包含许多重复的实例,然后您可以轻松修改合并条件

一个小的改进是提取一种合并实例的方法:

MyClass MergeInstances(IEnumerable<MyClass> instances){
     return instances.FirstOrDefault(myClass => myClass.ExtId.HasValue) 
          ?? instances.First(); //or whatever else you imagine
}
Run Code Online (Sandbox Code Playgroud)

现在只需在上面的代码中使用它

 var mergedInstances = idMap.Select(MergeInstances);
Run Code Online (Sandbox Code Playgroud)

干净、灵活、简单,没有附加条件。性能方面并不完美,但谁在乎呢。

编辑:由于性能是优先考虑的,所以还有更多选择

  1. 像上面一样进行查找,但仅针对较小的列表。然后迭代较大的值并进行所需的更改 O(m log m) + O(n)。m - 较小的列表大小,n - 较大的列表大小 - 应该是最快的。

  2. 按元素 id 对两个列表进行排序。创建一个 for 循环,迭代它们,并保持两个列表具有相同 id 的元素的当前索引。将索引移动到两个列表中找到的下一个最小的 id,如果只有一个,则仅将其移动。O(n log n) + O(m log m) + O(n);