【问题标题】:Combine two lists into one based on property根据属性将两个列表合二为一
【发布时间】:2015-11-04 10:25:33
【问题描述】:

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

MyClass 如下所示:

  • ID:int
  • 姓名:string
  • 分机号:int?

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

MyClass instance from List1
ID = someInt
Name = someString
ExtID = null

List2 中的 MyClass 实例

ID = someInt (same as List1)
Name = someString (same as List1)
ExtID = someInt

我基本上需要的是结合这两个列表,所以结果是一个包含:

ID = someInt (from List1)
Name = someString (from List1)
ExtID = someInt (null if no corresponding item - based on ID - on List2)

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

【问题讨论】:

  • 编写一个 Merge 函数,将具有相同 ID 的两个实例合并为一个 - 然后您可以连接列表,按 id 分组,最后使用合并函数折叠/减少组 -这就是我为了可读性而做的事情 - 为了提高性能,你可能对排序和循环没问题

标签: c# .net linq .net-4.5


【解决方案1】:

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

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

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

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

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

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

 var mergedInstances = idMap.Select(MergeInstances);

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

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

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

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

【讨论】:

  • 它会比 List2 上的 foreach 循环更快地处理 List1 中的相应项目并设置值吗? ;> 如果没有,那么由于它隐藏在库的私有方法中,我不介意使用 foreach 循环来获得更多性能,因为我不知道列表可以增长多大以及使用频率。我可能说错了,但优先级是性能。
  • 如果对于每个元素你需要搜索其他列表的元素,那么你会得到 O(n^2) 复杂度,上面的会更快,因为查找是 O(log n) * O(n) of Select 这是一个 foreach 循环,实际上是 O(n log n) vs O(n^)。我的胜利;)。无论如何,使用查找表(字典/地图)是一种方法。你也得到了灵活性。如果您可以改进现有的解决方案,您可以考虑从头开始提供字典而不是列表。
  • 您可以在其中一个列表上创建一个查找(或字典),然后使用查找来查找第二个列表以快速找到值。您也可以考虑对两个列表进行排序并执行一个智能循环,该循环只会遍历两个列表 O(n log n) + O(n)。
  • 如果名称是唯一的,您可以从一开始就基于名称而不是 ID。 :) 如果不是,那么它没有帮助,对于合并算法也无关紧要。因此,您要么按照上述方式进行操作,要么从较小的列表中进行查找并相互联系(我会说这是最快的方式)。或者,您按 id(或名称)对两者进行排序,并执行一个 for 循环,该循环同时遍历两个列表,以它们始终指向相同元素 id 的方式递增索引。我会把它包含在答案中
  • 很难说它是否会更快,它不会在列表(或任何其他可枚举)中打包元素,所以它应该更快,当然,如果你只期望一个元素,那么它就是适合使用:)
【解决方案2】:

这是你想要的吗

var joined = from Item1 in list1
         join Item2 in list2
         on Item1.Id equals Item2.Id // join on some property
         select new MyClass(Item1.Id, Item1.Name, Item1.ExtID??Item2.ExtID);

编辑:如果您正在寻找外连接,

var query = from Item1 in list1
            join Item2 in list2 on Item1.Id equals Item2.Id into gj
            from sublist2 in gj.DefaultIfEmpty()
            select new MyClass(Item1.Id, Item1.Name, sublist2??string.empty);

在可读性方面,使用 foreach 循环并不是一个坏主意..

【讨论】:

  • 因为它是一个内部连接,如果其中一个列表中缺少某些 id,它看起来就不起作用
  • 是的..true..添加了外连接案例
【解决方案3】:

我建议在该类的方法中创建 foreach 循环,所以每次你需要做这样的事情时,你会使用类似的东西

instanceList1.MergeLists(instanceList2)

使用此方法,您可以通过合并操作控制您想要的一切。

【讨论】:

  • @mikus LINQ 确实 使用迭代和迭代器,因此关于foreach 的评论不适用。另一方面,当 OP 询问如何编写该函数时,write your own function 不是一个好的答案。也许这应该被删除并作为评论重新发布?
  • 好吧,我只是假设他知道如何执行该功能,而我认为这是更好的方法。 (他说他不想要 foreach 是的,但我虽然他的意思是,他不想每次他需要进行合并时都这样做)
猜你喜欢
  • 2019-10-26
  • 1970-01-01
  • 1970-01-01
  • 2017-10-01
  • 2017-03-03
  • 1970-01-01
  • 2019-05-05
  • 2016-02-06
相关资源
最近更新 更多