查找不在列表 A 但列表 B 中的人员的更快方法 [重复]
Posted
技术标签:
【中文标题】查找不在列表 A 但列表 B 中的人员的更快方法 [重复]【英文标题】:faster way to find persons not in list A but list B [duplicate] 【发布时间】:2021-07-13 09:20:13 【问题描述】:今天我使用它来获取不在列表 A 中但在列表 B 中的人员列表。它有效,但似乎需要很长时间才能获得结果。有没有更快的方法来做同样的事情?
var missingPeople = listofPersons.Where(p => allUsedPersons.All(p2 => p2.Id != p.Id)).ToList();
【问题讨论】:
你想要(伪代码)B.Except(A)
吗?
您熟悉计算时间复杂度吗?
你试过Except吗? docs.microsoft.com/en-us/dotnet/api/…
allUsedPersons
是什么类型?
【参考方案1】:
您当前的实现有 O( n * m )
time complexity。
n
是listofPersons
的基数。
其中m
是allUsedPersons
的基数。
因此,如果您有 500 个listofPersons
和 200 个allUsedPersons
,您的代码将接受 100,000 次检查。 这很糟糕。
这是因为 Linq 的 Where
将针对 listofPersons
中的每个项目运行,并且在您的 Where
内部有 allUsedPersons.All
,它将运行 p2.Id != p.Id
检查 allUsedPersons
中的每个项目。李>
相反,使用HashSet<T>
在O(n)
时间构建一组已知值 - 然后您可以在O(1)
时间执行存在 检查。
listofPersons
和 200 个allUsedPersons
,我下面的代码将只需要 500 次检查。
100,000 与 500:找出差异。
HashSet<Int32> allPeopleIds = listofPersons.Select( p => p.Id ).ToHashSet();
List<Person> missingPeople = allUsedPersons
.Where( p => !allPeopleIds.Contains( p.Id ) )
.ToList();
在关系代数中(或者它是关系微积分?)你在做什么is known as an anti-join and Linq supports it via the Except
method,但是你需要定义一个自定义比较器,因为 Linq 没有 还有一个ExceptBy
方法(but MoreLinq does, though)。
【讨论】:
为什么是Int32
而不是int
?
@Dominik 这是我个人的偏好。 Int32
和 int
始终相同:***.com/questions/62503/should-i-use-int-or-int32
@TimSchmelter 很好,谢谢!
在Person
类中覆盖GetHashCode
(假设列表项是此类自定义类的实例)可能会提高性能。同样,将 Equals 覆盖到值比较并仅评估被认为相关的字段/道具,值得浪费一两个想法。哈希码的唯一性是Except、Union、Intersect 和大多数HashSet 操作性能的关键。
@Dai 你的方法比我做的要快得多。感谢您的解决方案和解释【参考方案2】:
另一种选择是提供自定义的、可重复使用的IEqualityComparer<Person>
:
public class PersonIdComparer : IEqualityComparer<Person>
public bool Equals(Person x, Person y)
return x?.Id == y?.Id;
public int GetHashCode(Person obj)
return obj?.Id ?? int.MinValue;
您可以将它用于许多 LINQ 方法。在这种情况下,您应该将其用于Except
:
var missingPeople = listofPersons.Except(allUsedPersons, new PersonIdComparer()).ToList();
Except
非常高效,因为它使用类似于HashSet
的集合。
【讨论】:
【参考方案3】:我认为以下方法可以帮助您提高性能:
List<int> listOfIds = allUsedPersons.select(c => c.Id).ToList();
var missingPeople = listofPersons.Where(r=>!listOfIds.Contains(r.Id));
【讨论】:
这个答案仍然是O(n*m)
,因为List<int>.Contains
仍然是O(n)
。您需要使用基于哈希表的集合(例如 HashSet<T>
或 Dictionary<K,V>
以获得 O(1)
性能)。或者,使用基于树的集合将为您提供O( log n )
性能,这仍然是一个改进,尽管对于这个特定的应用程序来说不是那么好。
感谢@Dai 的解释,我真的不知道,非常感谢。【参考方案4】:
将allUsedPersons.All()
之类的东西放在谓词中会不必要地增加迭代次数。事先准备好必填字段列表(此处为 p.id)并在谓词中使用它。
假设 allUsedPersons 是列表 A,下面会更快
var usedPersonsId = allUsedPersons.Select(p => p.id).ToList();
var missingPeople = listofPersons.Where(p => !usedPersonsId.Contains(p.Id)).ToList();
【讨论】:
这个答案 still 与另一个答案一样具有O(n*m)
时间复杂性:不要使用ToList()
。幸运的是,我要补充一点,如果您要对 usedPersonsId
进行排序,那么您可以使用二进制搜索而不是 Contains
检查,这将为您提供 O( log n )
运行时 - 更好,但仍然不是最好的@ 987654328@运行时。以上是关于查找不在列表 A 但列表 B 中的人员的更快方法 [重复]的主要内容,如果未能解决你的问题,请参考以下文章
二分查找会更快吗?Python中的二分查找与线性查找性能测试