虽然 Jon Skeet 的回答对于日常使用少量到中等数量的元素(最多几百万)的练习来说是一个极好的建议,但它不是最快的方法,也不是非常有效的资源。一个明显的缺点是,要获得完整的差异,需要对数据进行两次传递(如果相同的元素也很感兴趣,即使是 3 次)。显然,可以通过自定义重新实现 Except 方法来避免这种情况,但仍然存在创建哈希集需要大量内存并且计算哈希需要时间。
对于非常大的数据集(数十亿个元素),考虑特定情况通常是值得的。以下是一些可能会提供一些灵感的想法:
如果可以比较元素(在实践中几乎总是如此),那么对列表进行排序并应用以下 zip 方法是值得考虑的:
/// <returns>The elements of the specified (ascendingly) sorted enumerations that are
/// contained only in one of them, together with an indicator,
/// whether the element is contained in the reference enumeration (-1)
/// or in the difference enumeration (+1).</returns>
public static IEnumerable<Tuple<T, int>> FindDifferences<T>(IEnumerable<T> sortedReferenceObjects,
IEnumerable<T> sortedDifferenceObjects, IComparer<T> comparer)
{
var refs = sortedReferenceObjects.GetEnumerator();
var diffs = sortedDifferenceObjects.GetEnumerator();
bool hasNext = refs.MoveNext() && diffs.MoveNext();
while (hasNext)
{
int comparison = comparer.Compare(refs.Current, diffs.Current);
if (comparison == 0)
{
// insert code that emits the current element if equal elements should be kept
hasNext = refs.MoveNext() && diffs.MoveNext();
}
else if (comparison < 0)
{
yield return Tuple.Create(refs.Current, -1);
hasNext = refs.MoveNext();
}
else
{
yield return Tuple.Create(diffs.Current, 1);
hasNext = diffs.MoveNext();
}
}
}
这可以例如按以下方式使用:
const int N = <Large number>;
const int omit1 = 231567;
const int omit2 = 589932;
IEnumerable<int> numberSequence1 = Enumerable.Range(0, N).Select(i => i < omit1 ? i : i + 1);
IEnumerable<int> numberSequence2 = Enumerable.Range(0, N).Select(i => i < omit2 ? i : i + 1);
var numberDiffs = FindDifferences(numberSequence1, numberSequence2, Comparer<int>.Default);
在我的计算机上进行基准测试,N = 1M 的结果如下:
| Method |
Mean |
Error |
StdDev |
Ratio |
Gen 0 |
Gen 1 |
Gen 2 |
Allocated |
| DiffLinq |
115.19 ms |
0.656 ms |
0.582 ms |
1.00 |
2800.0000 |
2800.0000 |
2800.0000 |
67110744 B |
| DiffZip |
23.48 ms |
0.018 ms |
0.015 ms |
0.20 |
- |
- |
- |
720 B |
对于 N = 100M:
| Method |
Mean |
Error |
StdDev |
Ratio |
Gen 0 |
Gen 1 |
Gen 2 |
Allocated |
| DiffLinq |
12.146 s |
0.0427 s |
0.0379 s |
1.00 |
13000.0000 |
13000.0000 |
13000.0000 |
8589937032 B |
| DiffZip |
2.324 s |
0.0019 s |
0.0018 s |
0.19 |
- |
- |
- |
720 B |
请注意,此示例当然受益于列表已经排序并且可以非常有效地比较整数这一事实。但这正是重点:如果您确实有有利的环境,请确保利用它们。
一些进一步的cmets:比较函数的速度显然与整体性能相关,因此对其进行优化可能是有益的。这样做的灵活性是压缩方法的一个好处。此外,并行化对我来说似乎更可行,虽然绝不容易,而且可能不值得付出努力和开销。然而,将过程加速大约 2 倍的简单方法是将列表分别分成两半(如果可以有效地完成)并并行比较各部分,一个从前到后处理,另一个处理以相反的顺序。