使用LINQ创建临时工作变量?

时间:2013-03-01 08:02:41

标签: c# linq

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

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

预期结果:

{ 1 }

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

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

问题是这不止一次迭代。使用正常的for循环,我们可以达到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中声明我的“新HashSet”。我在想firstList.Aggregate((c, d = new HashSet<int>) => ..

3 个答案:

答案 0 :(得分:3)

一种简单的方法是:

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

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

答案 1 :(得分:2)

Evelie,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();

并且,通常值得花费很少的努力将其包装到新的扩展中。请注意,所有这些代码几乎与您和其他人提供的代码完全相同。它只是“很好地包装”成“变量 - 隐藏”或“扩展”。

编辑:是的,这是真的,所有带有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();

但这主要是.distinct()无论如何都会做的。

答案 2 :(得分:1)

从技术上讲,你可以做到这一点(只是你所做的更短的方式):

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

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

修改

根据Jon 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)