保留重复项的两个列表之间的差异

9

我有两个列表:

var list1 = new List<string> { "A", "A", "B", "C" };
var list2 = new List<string> { "A", "B" };

我希望能够制作一个类似的列表:

var result = new[] { "A", "C" };

这里的列表是从list2中删除了所有list1元素,我认为没有Linq扩展方法可以实现这个功能,因为Except会删除重复项。

非Linq的方法是:

var tempList = list1.ToList();
foreach(var item in list2)
{
    tempList.Remove(item);
}

但我想知道是否有我可能错过的Linq扩展方法。

编辑:

既然可能没有,这里是我制作的一个扩展方法。

public static class LinqExtensions
{
    public static IEnumerable<T> RemoveRange<T>(this IEnumerable<T> source, IEnumerable<T> second)
    {
        var tempList = source.ToList();
            
        foreach(var item in second)
        {
            tempList.Remove(item);
        }
        
        return tempList;
    }
    
    public static IEnumerable<TFirst> RemoveMany<TFirst, TSecond>(this IEnumerable<TFirst> source, IEnumerable<TSecond> second, Func<TSecond, IEnumerable<TFirst>> selector)
    {
        var tempList = source.ToList();
            
        foreach(var item in second.SelectMany(selector))
        {
            tempList.Remove(item);
        }
        
        return tempList;
    }
}

使用方法:

list1.RemoveRange(list2)
3个回答

2

根据您的示例,我认为您的意思是“从list1中删除所有来自list2的元素”:

var lookup2 = list2.ToLookup(str => str);

var result = from str in list1
             group str by str into strGroup
             let missingCount 
                  = Math.Max(0, strGroup.Count() - lookup2[strGroup.Key].Count())
             from missingStr in strGroup.Take(missingCount)
             select missingStr;

列表的大小是任意的,因此其中任何一个都可能更大。 - Dustin Kingen
这里没有对列表的相对大小做出任何假设。 - Ani
哦,我想我误解了你所说的“从列表2中删除所有元素并将它们从列表1中移除”的意思。 - Dustin Kingen
我选择这个答案作为正确答案,因为它在linqpad中运行比@dasblinkenlight的Test Code更快。 - Dustin Kingen
@Ani 这太棒了! - reggaeguitar

2
不是LINQ,但仍只需一行代码:
list2.ForEach(l => list1.Remove(l));

顺便说一下... 如果 List<int> 有类似于 AddRange 的东西,但可以同时删除一堆项目,那就太好了。


1

如果您不关心结果元素的顺序,您可以使用LINQ的GroupBy来实现:

var a = new List<string>{"A","A", "B", "C"};
var b = new List<string>{"A", "B"};
var res  =  a.Select(e => new {Key=e, Val=1})
    .Concat(b.Select(e => new {Key=e, Val=-1}))
    .GroupBy(e => e.Key, e => e.Val)
    .SelectMany(g => Enumerable.Repeat(g.Key, Math.Max(0, g.Sum())))
    .ToList();

这里有一个ideone演示

我必须承认,你的解决方案比我的简单得多,因此应该被视为一种好奇心,一种证明LINQ也可以完成这个任务的方法。

它的工作原理如下:对于第一个列表中的每个元素,我们添加一个键值对,其值为1;对于第二个列表中的每个元素,我们添加一个键值对,其值为-1。然后,我们按照它们的键将所有元素分组,总计它们的1和负数-1,并产生与总数相同的键,确保在结果为负数时不选择任何内容。


@dasblinkenlight 变成一个扩展程序,以便为他简化它。 - Brad Rem
2
我想有时候 Linq 并不是答案。 - Dustin Kingen
1
@Romoku 你说得对。用一句Stack Overflow的梗来说,有时候会变成这样:“我有一个问题;我想用Linq来解决它;现在我有两个问题” :) - Sergey Kalinichenko
用X替换Linq,你就得到了这个模因的一般形式。 - Dustin Kingen
@Romoku 原始的梗是关于正则表达式的,但这个梗确实非常通用。 - Sergey Kalinichenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接