- 显示在第一个列表中但不显示在第二个列表中的项目
- 显示在第二个列表中但不显示在第一个列表中的项目
var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();
但这个程序的性能不如我所期望的好。 有什么办法可以让它更快速、资源占用更少,因为我需要处理很多列表?
var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();
但这个程序的性能不如我所期望的好。 有什么办法可以让它更快速、资源占用更少,因为我需要处理很多列表?
使用 Except
函数:
var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();
我怀疑还有一些方法比这个稍微快一点,但即使是这个方法,速度也会比你的O(N * M)方法快得多。
如果你想将它们组合起来,你可以创建一个以上述方法为基础的方法,并加上一个返回语句:
return !firstNotSecond.Any() && !secondNotFirst.Any();
需要注意的一点是,在问题中的原始代码和这里的解决方案之间存在结果差异:只在一个列表中出现的重复元素将只报告一次,而在原始代码中它们将根据出现次数报告多次。
例如,对于列表[1,2,2,2,3]
和[1]
,原始代码中“列表1中但不在列表2中的元素”结果将是[2, 2, 2, 3]
。 针对我的代码,它只会是[2, 3]
。 在许多情况下,这不会成为问题,但值得注意。
Equals(object)
并/或实现了 IEquatable<T>
,那么它应该是可以的。 - Jon SkeetIEquatable<T>
实现或object.Equals(object)
方法。听起来你应该创建一个带有[mcve]的新问题 - 我们无法在评论中诊断问题。 - Jon SkeetEnumerable.SequenceEqual 方法
根据相等比较器,确定两个序列是否相等。 MS.Docs
Enumerable.SequenceEqual(list1, list2);
对于所有原始数据类型都适用。如果您需要在自定义对象上使用它,则需要实现IEqualityComparer
定义支持比较对象是否相等的方法。
IEqualityComparer接口
定义支持比较对象是否相等的方法。 MS.Docs for IEqualityComparer
SequenceEqual
的结果是一个简单的布尔值,我不认为它能成为答案。原帖中要求得到两个列表的结果,并用集合运算来描述需要的结果:“第一个列表中出现但第二个列表中没有的项”。没有表明顺序很重要,而SequenceEqual
会考虑顺序,这似乎回答了完全不同的问题。 - Jon Skeet更高效的方法是使用Enumerable.Except
:
var inListButNotInList2 = list.Except(list2);
var inList2ButNotInList = list2.Except(list);
此方法是通过使用延迟执行实现的。这意味着你可以编写如下代码:
var first10 = inListButNotInList2.Take(10);
由于内部使用了Set<T>
来比较对象,因此它也很高效。 它首先从第二个序列中收集所有不同的值,然后流式处理第一个序列的结果,检查它们之前是否已经出现过。
Set<T>
是从第二个序列构建的(即完全迭代并存储),然后可以从第一个序列添加的项被产生。 - spenderWhere
的执行是部分延迟的,因为在list.Where(x => x.Id == 5)
中数字5
的值是在开始时存储的,而不是惰性地执行。 - jwg如果您希望结果不区分大小写,可以使用以下方法:
List<string> list1 = new List<string> { "a.dll", "b1.dll" };
List<string> list2 = new List<string> { "A.dll", "b2.dll" };
var firstNotSecond = list1.Except(list2, StringComparer.OrdinalIgnoreCase).ToList();
var secondNotFirst = list2.Except(list1, StringComparer.OrdinalIgnoreCase).ToList();
firstNotSecond
将包含b1.dll
secondNotFirst
将包含b2.dll
Except
方法来避免这个问题,但是创建哈希集合需要大量内存,并且计算哈希需要时间。/// <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);
方法 | 平均值 | 误差 | 标准偏差 | 比率 | Gen 0 | Gen 1 | Gen 2 | 分配的内存 |
---|---|---|---|---|---|---|---|---|
DiffLinq | 115.19 毫秒 | 0.656 毫秒 | 0.582 毫秒 | 1.00 | 2800.0000 | 2800.0000 | 2800.0000 | 67110744 B |
DiffZip | 23.48 毫秒 | 0.018 毫秒 | 0.015 毫秒 | 0.20 | - | - | - | 720 B |
方法 | 用时 | 误差 | 标准差 | 比率 | Gen 0 | Gen 1 | Gen 2 | 已分配内存 |
---|---|---|---|---|---|---|---|---|
DiffLinq | 12.146 秒 | 0.0427 秒 | 0.0379 秒 | 1.00 | 13000.0000 | 13000.0000 | 13000.0000 | 8589937032 字节 |
DiffZip | 2.324 秒 | 0.0019 秒 | 0.0018 秒 | 0.19 | - | - | - | 720 字节 |
注意,此示例当然受益于列表已排序并且整数可以非常高效地比较。但这正是关键:如果您有有利的条件,请确保利用它们。
进一步的说明:比较函数的速度显然对整体性能很重要,因此优化它可能会很有益。压缩方法的灵活性就是其中的一个好处。此外,对我来说,并行化似乎更可行,虽然绝不容易,可能并不值得投入工作和开销。尽管如此,加速过程的简单方法是将两个列表分别拆成两半(如果可以高效地完成),并以并行方式进行比较,一个从前往后处理,另一个从后往前处理,总速度大约提高2倍。
using System.Collections.Generic;
using System.Linq;
namespace YourProject.Extensions
{
public static class ListExtensions
{
public static bool SetwiseEquivalentTo<T>(this List<T> list, List<T> other)
where T: IEquatable<T>
{
if (list.Except(other).Any())
return false;
if (other.Except(list).Any())
return false;
return true;
}
}
}
用法:
public sealed class Car : IEquatable<Car>
{
public Price Price { get; }
public List<Component> Components { get; }
...
public override bool Equals(object obj)
=> obj is Car other && Equals(other);
public bool Equals(Car other)
=> Price == other.Price
&& Components.SetwiseEquivalentTo(other.Components);
public override int GetHashCode()
=> Components.Aggregate(
Price.GetHashCode(),
(code, next) => code ^ next.GetHashCode()); // Bitwise XOR
}
Component
类是什么,这里展示的 Car
方法都应该以几乎相同的方式实现。IEquatable
,Equals
和 GetHashCode
必须以逻辑兼容的方式操作实例的属性。两个具有相同内容的列表仍然是不同的对象,并且将产生不同的哈希码。由于我们希望将这两个列表视为相等,因此必须让 GetHashCode
为它们的每个列表产生相同的值。我们可以通过将哈希码委托给列表中的每个元素,并使用标准位异或运算符将它们组合起来来实现这一点。异或是无序的,因此无论列表是否排序不同,它都不重要。唯一需要的是它们只包含等效成员。虽然这不是这个问题的解决方案,但以下代码可以比较列表中相等但不完全相同的对象:
public class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> where T : IEquatable<T>
/// <summary>
/// True, if this contains element with equal property-values
/// </summary>
/// <param name="element">element of Type T</param>
/// <returns>True, if this contains element</returns>
public new Boolean Contains(T element)
{
return this.Any(t => t.Equals(element));
}
/// <summary>
/// True, if list is equal to this
/// </summary>
/// <param name="list">list</param>
/// <returns>True, if instance equals list</returns>
public Boolean Equals(EquatableList<T> list)
{
if (list == null) return false;
return this.All(list.Contains) && list.All(this.Contains);
}
Except
。 - Pranav Singh试试这种方式:
var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
.Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));
var set1 = new HashSet<T>(list1);
var set2 = new HashSet<T>(list2);
var areEqual = set1.SetEquals(set2);
T代表列表元素的类型。
我使用了这个代码来比较两个拥有数百万条记录的列表。
这种方法不需要太多时间。
//Method to compare two list of string
private List<string> Contains(List<string> list1, List<string> list2)
{
List<string> result = new List<string>();
result.AddRange(list1.Except(list2, StringComparer.OrdinalIgnoreCase));
result.AddRange(list2.Except(list1, StringComparer.OrdinalIgnoreCase));
return result;
}