如果您有两个已排序的集合,您可以实现比使用LINQ提供的任何东西更快的交集。
基本上,保持两个IEnumerator<T>
游标打开,一个用于每个集合。在任何时候,推进较小值的那个游标。如果它们在任何点匹配,同时推进它们,直到达到任一迭代器的末尾为止。
这样做的好处是,您只需要遍历每个集合一次,而且可以在O(1)内存中完成。
下面是一个示例实现-未经测试,但编译通过 :) 它假定传入的序列都是无重复项且已排序的,都根据提供的比较器排序(传入Comparer<T>.Default
):
(答案结尾还有更多文本!)
static IEnumerable<T> IntersectSorted<T>(this IEnumerable<T> sequence1,
IEnumerable<T> sequence2,
IComparer<T> comparer)
{
using (var cursor1 = sequence1.GetEnumerator())
using (var cursor2 = sequence2.GetEnumerator())
{
if (!cursor1.MoveNext() || !cursor2.MoveNext())
{
yield break;
}
var value1 = cursor1.Current;
var value2 = cursor2.Current;
while (true)
{
int comparison = comparer.Compare(value1, value2);
if (comparison < 0)
{
if (!cursor1.MoveNext())
{
yield break;
}
value1 = cursor1.Current;
}
else if (comparison > 0)
{
if (!cursor2.MoveNext())
{
yield break;
}
value2 = cursor2.Current;
}
else
{
yield return value1;
if (!cursor1.MoveNext() || !cursor2.MoveNext())
{
yield break;
}
value1 = cursor1.Current;
value2 = cursor2.Current;
}
}
}
}
编辑:如评论中所述,某些情况下您可能有一个比另一个大得多的输入,这种情况下,您可以在较大的集合中为每个元素使用二分查找来节省大量时间。但是,这需要随机访问较大的集合(这只是二分查找的先决条件)。您甚至可以通过使用上一个结果的匹配项为二分查找提供一个下限来使其略微优于朴素的二分查找。所以假设你正在寻找值1000、2000和3000,在一个从0到19,999的整数中的集合中。在第一次迭代中,您需要搜索整个集合-您的起始低/高索引将分别为0和19,999。然而,在找到1000的匹配项后,下一个步骤(在这里您正在寻找2000)可以从较低的索引2000开始。随着进展,您需要搜索的范围逐渐缩小。然而,是否值得花费额外的实现成本是另一回事。