使用LINQ创建临时工作变量



问题:我有一个int列表,我想得到存在两次或更多次的数字。

List<int> firstList = new List<int> { 1, 1, 3 };

预期结果:

{ 1 }

这可以通过LINQ轻松完成。。例如,这个

var result = firstList.Where(c => firstList.Count(d => c == d) > 1).Distinct();

问题是,这会进行不止一次的迭代。对于循环的法线,我们可以达到O(N(的时间。。

List<int> result = new List<int>();
HashSet<int> doubles = new HashSet<int>();
foreach (var v in firstList)
{
    if (!doubles.Contains(v))
        doubles.Add(v);
    else
        result.Add(v);
}

这就是我们想用linq aswel做的。。。

HashSet<int> doubles = new HashSet<int>();
var result = firstList.Where((c) => doubles.Contains(c) ? true : !doubles.Add(c)).ToList();

这是我唯一能想到的办法。。

问题:有没有什么方法可以在LINQ中声明我的"新哈希集"。我在想firstList.Aggregate((c, d = new HashSet<int>) =>之类的东西。。

一个简单的方法是:

var repeated = list.GroupBy(x => x)
                   .Where(g => g.Count() > 1)
                   .Select(g => g.Key);

它只会迭代一次——它的效率略低于手工制作的解决方案,但应该非常合理——而且非常简单。

Eveline,Eren和John的答案都是正确的,是你能得到的最简单的答案。在LINQ的"漂亮"语法中,有一个let关键字可以让你介绍一些东西,但它主要是由编译器以类似于Eren帖子中的var hashset的方式重写的。正如不能"隐藏"源firstList一样,通常也不能隐藏其他支持变量。至少,以一种理智的方式

疯狂的方式存在。我所说的疯狂是指可读性差得多,晦涩难懂。

例如,让我们用变量隐藏重写Eren的例子:

var firstList = new[] { 1, 1, 3 };
var result = Enumerable.Repeat(new { list = firstList, hash = new HashSet<int>() }, 1)
                .Select(anon => anon.list.Where(x => !anon.hash.Add(x)))
                .SelectMany(_ => _);

但这值得吗?

此外,请不要将自己局限于标准LINQ运算符。你可以很容易地介绍你自己的:

public static class MyOps
{
    public static IEnumerable<T> FindDuplicates<T>(this IEnumerable<T> input)
    {
        var hashSet = new HashSet<T>();
        foreach (var item in input)
            if (!hashSet.Add(item))
                yield return item;
    }
}
var firstList = new[] { 1, 1, 3 };
var result1 = firstList.FindDuplicates();

而且,将它封装到一个新的扩展中通常是值得的。请注意,所有这些代码与您和其他人之前提供的代码几乎相同。它只是被"很好地包装"到"变量隐藏器"或"扩展"中。

Edit:是的,这是真的,所有带有hashset的示例都将返回所有重复项。您可以通过两个哈希集来进行区分,而不是区分:一个用于重复检查,另一个用于过滤重复结果。

public static class MyOps
{
    public static IEnumerable<T> FindDuplicates<T>(this IEnumerable<T> input)
    {
        var hashSet1 = new HashSet<T>();
        var hashSet2 = new HashSet<T>();
        foreach (var item in input)
            if (!hashSet1.Add(item)) // note the negation
                if (hashSet2.Add(item)) // note NO negation
                    yield return item;
    }
}
var firstList = new[] { 1, 1, 3 };
var result1 = firstList.FindDuplicates();

但这主要是.dispinct((无论如何都会做的事情。

从技术上讲,你可以这样做(只是你正在做的事情的一种更短的方式(:

var hashSet = new HashSet<int>();
var filtered = firstList
    .Where(x =>
    {
        if (hashSet.Contains(x)) return true;
        hashSet.Add(x);
        return false;
    });

但我认为最好避免这种副作用,只使用上面Jon Skeet的方法(安全地假设它在上面:(

编辑:

根据Jon Skeet在下面的评论,这甚至可以缩短为:

var hashSet = new HashSet<int>();
var filtered = firstList.Where(x => !hashSet.Add(x));

请注意,您需要小心使用一次。例如:

var list1 = filtered.ToList(); // correct result
var list2 = filtered.ToList(); // incorrect result (returns all numbers)

最新更新