C# 具有非唯一项的两个列表之间的差异
我有两种刺痛。一个表示代码可以运行的函数类型,另一个表示将运行这些函数的代理。这两个列表应该是1:1的关系,但是当远程服务请求更多功能时,我需要找出这两个列表之间的区别。 问题是这些条目是非唯一的,因此我不能只调用C# 具有非唯一项的两个列表之间的差异,c#,linq,C#,Linq,我有两种刺痛。一个表示代码可以运行的函数类型,另一个表示将运行这些函数的代理。这两个列表应该是1:1的关系,但是当远程服务请求更多功能时,我需要找出这两个列表之间的区别。 问题是这些条目是非唯一的,因此我不能只调用list1.RemoveAll(list2),因为这将删除list2中包含的具有相同值的所有条目,而不是每个条目一个 这就是我需要的: {a,a,a,a,b,b,c} - {a,a,b,c} = {a,a,b} 我现在就是这样做的: var difference = list1.To
list1.RemoveAll(list2)
,因为这将删除list2中包含的具有相同值的所有条目,而不是每个条目一个
这就是我需要的:
{a,a,a,a,b,b,c} - {a,a,b,c} = {a,a,b}
我现在就是这样做的:
var difference = list1.ToList();
foreach (var entry in list2)
{
difference.Remove(entry);
}
它的功能和工作,但它打破了Linq的使用,我通过其余的代码
我试图找到一种方法并在网上搜索,但没有找到一种使用Linq的方法。在长集合(顺序)的情况下,嵌套循环和Remove
可能无效(从O(N*M)
到O(N*N*M)
),你可以尝试分组和字典O(N+M)
时间复杂性。请注意,实现没有保持初始顺序({a,b,b,a}-{b}={a,a,b}
,而不是{a,b,a}
):
现在让马奔跑:
Stopwatch watch = new Stopwatch();
watch.Start();
// Hash solution
var counts = right
.GroupBy(item => item)
.ToDictionary(chunk => chunk.Key, chunk => chunk.Count());
var result = left
.GroupBy(item => item)
.SelectMany(chunk => chunk.Skip(counts.TryGetValue(chunk.Key, out var skip) ? skip : 0))
.ToList();
watch.Stop();
TimeSpan tHash = watch.Elapsed;
watch.Reset();
watch.Start();
// Initial solution
var difference = left.ToList();
foreach (var entry in right) {
difference.Remove(entry);
}
watch.Stop();
TimeSpan tInitial = watch.Elapsed;
Console.Write($"Hash: {tHash}; Initial {tInitial}");
结果(核心i7 3.6GHz)11毫秒
vs.1.4秒
Hash: 00:00:00.0111296; Initial 00:00:01.3957468
我不确定您的代码是否符合您的要求:
var list1 = {b, b, c, a};
var list2 = {a, b, b, c};
代码将删除所有元素,即使第二个列表中的顺序与第一个列表中的顺序不同
var list1 = {a, b, a, c, a};
var list2 = {a, a, b, c};
var list3 = {b, c, a, a}
list1-list2和list1-list3将具有相同的输出:
result = {b, c, a}
这是你想要的吗?订单重要吗
此外,您的代码会更改输入数据。LINQ用于查询数据,任何LINQ函数都不会更改输入数据。如果确实希望代码更改输入数据,则无法将其转换为类似LINQ的函数
但是,如果您不想更改输入序列,我们可以使用一个新函数“扩展”IEnumerable的功能,该函数可以像处理LINQ函数一样处理您的功能,但不更改输入序列除外
该函数将有两个IEnumerable
作为输入,并返回一个IEnumerable
作为输出。输入序列不变
看
如果这是您想要的,让我们实现它
public static IEnumerable<TSource> RemoveDuplicates<TSource> (
this IEnumerable<Tsource> list1,
IEnumerable<TSource> list2)
{
var differenct = list1.ToList();
foreach (var entry in list2)
{
difference.Remove(entry);
}
return difference;
}
请注意,由于yield语句,此函数使用延迟执行。只有当您开始枚举LINQ时,它才会被执行
因为要删除的“a”可能是list2中的最后一个,所以要获得第一个返回的元素,我们必须枚举list2的所有元素,以检查第一个“a”是否在list2中的任何位置。因为我记得这个枚举的结果,所以不必再次枚举list2来返回第二个(和任何其他)元素。您的方法简单而好。为什么要把它改成Linq?编辑:删除了注释的最后一部分。因为现在它是一个类似于else-Linq-only代码的大代码块,使得它更难阅读。我还可以将一个方法转换为一个返回调用,将其作为Linq而不是当前的两个。列表的顺序是否重要(或者,它总是按字母顺序排序)?是否会出现list2
中的某些内容在list1
中不存在(以相同的多重性),即循环中的Remove
调用将返回false
,而不删除任何内容?看起来您所拥有的可能被建模为具有多集的自然“集差异”的多集。由于代码的性质,list2保证比list1小。list2从0开始构建,以满足list1的要求。列表是否保证被排序?这解决了Linq的问题,但我怀疑它比我当前的列表性能更好,因为您浏览列表的次数比我的代码多。谢谢你的尝试。@Gregor A.Lamche:如果列表很短,那么你是对的(它们不值得哈希优化);然而,在长列表的情况下,哈希将做得更好。
result = {b, c, a}
static class EnumerableExtensions
{
public static IEnumerable<TSource> RemoveDuplicates<TSource> (
this IEnumerable<Tsource> list1,
IEnumerable<TSource> list2)
{
// TODO: implement
}
}
IEnumerable<string> list1 = ...
IEnumerable<string> list2 = ...
IEnumerable<string> result = list1.RemoveDuplicates(list2);
var result = list1.Where(x => x.StartsWith("a")
.RemoveDuplicates(list2.Where(x => x.EndsWith("z")
.Select(x => ...)
.ToList();
public static IEnumerable<TSource> RemoveDuplicates<TSource> (
this IEnumerable<Tsource> list1,
IEnumerable<TSource> list2)
{
var differenct = list1.ToList();
foreach (var entry in list2)
{
difference.Remove(entry);
}
return difference;
}
public static IEnumerable<TSource> RemoveDuplicates<TSource> (
this IEnumerable<Tsource> list1,
IEnumerable<TSource> list2)
{
var group1 = list1.GroupBy(item => item)
.Select(group => new
{
value = group.Key,
count = group.Count(),
});
var group2 = list2.GroupBy(item => item)
.Select(group => new
.ToDictionary(group => group.Key, group => group.Count());
// for every item in group1, check if there is a same one in group2.
// If so, subtract the count and return the remaining items
foreach (var item in group1)
{
// are the also some "a" values in list2?
if (group2.TryGetValue(item1.Value, out int nrToremove))
{
// yes there are: nrToRemove contains the number of "a" values in list2
int nrToReturn = item.Count - nrToRemove;
// return all remaining "a" values:
for (int i=0; i<nrToReturn; ++i)
{
yield return item.Value; // return an "a"
}
}
}
}