Difference between two lists preserving duplicates
I have two lists:
var list1 = new List<string> { "A", "A", "B", C" };
var list2 = new List<string> { "A", "B" };
and I would like to produce a list like
var result = new[] { "A", "C" };
Where the list is all the elements from list1
removed from list2
I don't think there is a Linq extension method for this since Except
removes duplicates.
The non-linq way to do this would be:
var tempList = list1.ToList();
foreach(var item in list2)
{
tempList.Remove(item);
}
but I am wondering if there is a Linq extension method that I might have missed.
Edit:
Since there probably aren't any here's an extension method I made.
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;
}
}
Usage:
list1.RemoveRange(list2)
看你的例子,我认为你的意思是“从列表1中删除了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;
If you do not care about the order in which the elements of the result come, you can do it with LINQ's 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();
Here is a demo on ideone.
I must admit that your solution is far simpler than mine, so it should be regarded as a mere curiosity, a way to prove that this could be done with LINQ too.
Here is how it works: for each element from the first list we add a key-value pair with a 1
; for each element from the second list we add a key-value pair with -1
. Then we group all elements by their key, total up their ones and negative ones, and produce as many keys as the total, making sure that we do not select anything when the result is negative.
Not LINQ, but a one-line anyway:
list2.ForEach(l => list1.Remove(l));
btw... it would be nice if List<int>
had something like AddRange
but to remove a bunch of items at the same time.
下一篇: 两个列表之间的区别保留重复