1

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

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>) =>..

4

3 回答 3

3

一种简单的方法是:

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

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

于 2013-03-01T08:06:14.460 回答
2

Evelie,Eren 和 John 的答案都是正确的,它们是你能得到的最简单的。在 LINQ 的“漂亮”语法中,有一个let关键字可以让您引入一些东西,但这主要是由编译器以类似于var hashsetEren 帖子中看到的方式重写的。正如您不能“隐藏”源一样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() 无论如何都会做的。

于 2013-03-01T08:47:47.093 回答
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 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)
于 2013-03-01T08:17:37.430 回答