C# 具有非唯一项的两个列表之间的差异

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

我有两种刺痛。一个表示代码可以运行的函数类型,另一个表示将运行这些函数的代理。这两个列表应该是1:1的关系,但是当远程服务请求更多功能时,我需要找出这两个列表之间的区别。 问题是这些条目是非唯一的,因此我不能只调用
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"
            }
        }
    }
}