我有两个刺痛。一种代表我的代码可以运行的功能类型,另一种代表可以运行这些功能的代理。这两个列表应该是一对一的关系,但是当远程服务需要更多功能时,我需要找出这两个列表之间的区别。
问题在于条目不是唯一的,所以我不能只调用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进行搜索的方法。
答案 0 :(得分:4)
如果 long 个集合(序列)的嵌套循环和Remove
无效(从O (N * M)
到O(N * N * M)
),则可以尝试{{1 }}和grouping
,时间复杂度为dictionary
。请注意,实现不保留初始顺序(O (N + M)
,而不是{a, b, b, a} - {b} == {a, a, b}
)
{a, b, a}
编辑:创建基准很容易;如果是长序列(List<char> left = new List<char>() { 'a', 'a', 'a', 'a', 'b', 'b', 'c' };
List<char> right = new List<char>() { 'a', 'a', 'b', 'c' };
var counts = right
.GroupBy(item => item)
.ToDictionary(chunk => chunk.Key, chunk => chunk.Count());
var difference = left
.GroupBy(item => item)
.SelectMany(chunk => chunk.Skip(counts.TryGetValue(chunk.Key, out var skip) ? skip : 0))
.ToList();
),则以 hash (N = 200000
,GroupBy
)为准;确保如果您没有那么长的收藏夹,则可以放心地保留初始代码:
Dictionary
现在让马跑:
Random rnd = new Random(1);
int N = 200000;
List<char> left = Enumerable
.Range(0, N)
.Select(index => (char)(rnd.Next('z' - 'a') + 'a'))
.ToList();
List<char> right = Enumerable
.Range(0, N)
.Select(index => (char)(rnd.Next('z' - 'a') + 'a'))
.ToList();
结果(Core i7 3.6GHz)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}");
与11 ms
1.4 second
答案 1 :(得分:1)
我不确定您的代码是否满足您的要求:
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<TSource>
作为输入,并返回一个IEnumerable<TSource>
作为输出。输入顺序不变。
请参见Extension Methods Demystified
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);
或者在复杂的LINQ函数中:
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;
}
这不是很有效。例如,如果您只想用结果Any()
或FirstOrDefault()
,则计算完整列表效率不高。
如果结果的顺序并不重要,则可以按相同的字符串对输入进行分组,并仅返回元素数量之差。因此,如果list1有5个“ a”,而list2有3个“ a”,则返回2次“ a”。为此,您可以对字符串进行分组,并计算每个组中的项目数。
为简单起见,我的评论就像我们从您的输入中删除了几个“ a”值
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"
}
}
}
}
请注意,由于yield语句,此函数使用延迟执行。仅当您开始枚举LINQ时,它将被执行。
因为要删除的“ a”可能是list2中的最后一个元素,所以要获取第一个返回的元素,我们必须枚举list2的所有元素以检查第一个“ a”是否在list2中的任何位置。因为我记得该枚举的结果,所以不必再次枚举list2即可返回第二个(和其他任何元素)。