如何快速判断列表是否仅包含重复项?

时间:2010-11-15 15:17:38

标签: c# .net list duplicates mahjong

有多个相关问题,但我正在寻找针对我的案例的解决方案。有一组(通常)14个整数。如何快速判断每个int是否恰好出现两次(即有7对)?值范围从1到35.这里的主要方面是性能。

作为参考,这是我目前的解决方案。它的编写尽可能地与规范相似,没有考虑到性能,所以我确信可以大大改进:

var pairs = Array
    .GroupBy (x => x)
    .Where (x => x.Count () == 2)
    .Select (x => x.ToList ())
    .ToList ();
IsSevenPairs = pairs.Count == 7;

使用Linq是可选的。我不在乎如何,只要它很快:)

编辑:有一种特殊情况,一个int出现2次,其中n> 1.在这种情况下,检查失败,即应该有7对不同。

编辑:结果 我通过微小的修改测试了Ani和Jon的解决方案,并在目标应用程序的多个基准测试运行期间发现,Ani在我的机器上有大约两倍Jon的吞吐量(在Win7-64上有一些Core 2 Duo)。生成整数数组已经需要大约相应的检查,所以我对结果感到满意。谢谢,全部!

6 个答案:

答案 0 :(得分:10)

嗯,鉴于您的具体要求,我们可以更聪明一些。像这样:

public bool CheckForPairs(int[] array)
{
    // Early out for odd arrays.
    // Using "& 1" is microscopically faster than "% 2" :)
    if ((array.Length & 1) == 1)
    {
        return false;
    }

    int[] counts = new int[32];
    int singleCounts = 0;
    foreach (int item in array)
    {
        int incrementedCount = ++counts[item];
        // TODO: Benchmark to see if a switch is actually the best approach here
        switch (incrementedCount)
        {
            case 1:
                singleCounts++;
                break;
            case 2:
                singleCounts--;
                break;
            case 3:
                return false;
            default:
                throw new InvalidOperationException("Shouldn't happen");
        }
    }
    return singleCounts == 0;
}

基本上,这会记录您仍然拥有多少未配对的值,并且如果它找到三种不同的值,则会“早出”。

(我不知道这是否比Ani的增量方法更快或更慢,然后检查不匹配的对。)

答案 1 :(得分:6)

显然,LINQ不会在这里提供最佳解决方案,尽管我会将您当前的LINQ解决方案改进为:

// checks if sequence consists of items repeated exactly once
bool isSingleDupSeq = mySeq.GroupBy(num => num)
                           .All(group => group.Count() == 2);

// checks if every item comes with atleast 1 duplicate
bool isDupSeq = mySeq.GroupBy(num => num)
                     .All(group => group.Count() != 1);

对于您提到的特定情况(0 - 31),这是一个更快的,基于阵列的解决方案。当可能的数字范围很大时,它不能很好地扩展(在这种情况下使用散列解决方案)。

// elements inited to zero because default(int) == 0
var timesSeenByNum = new int[32];

foreach (int num in myArray)
{
    if (++timesSeenByNum[num] == 3)
    {
        //quick-reject: number is seen thrice
        return false;
    }
}

foreach (int timesSeen in timesSeenByNum)
{
    if (timesSeen == 1)
    {
        // only rejection case not caught so far is
        // if a number is seen exactly once
        return false;
    }
}

// all good, a number is seen exactly twice or never
return true;   

编辑:修正了Jon Skeet指出的错误。我还应该指出,他的算法更聪明,可能更快。

答案 2 :(得分:0)

我会创建一个由32个整数元素组成的数组,初始化为零。我们称之为“比利”。

对于输入数组的每个元素,我将增加billy [element] 1。

最后,检查billy是否仅包含0或2。

答案 3 :(得分:0)

当你只有14-areh对并且只有32-areh可能的值时,几乎肯定是矫枉过正,但在一般情况下你可以这样做:

bool onlyPairs = yourArray.ContainsOnlyPairs();

// ...

public static class EnumerableExtensions
{
    public static bool ContainsOnlyPairs<T>(this IEnumerable<T> source)
    {
        var dict = new Dictionary<T, int>();

        foreach (T item in source)
        {
            int count;
            dict.TryGetValue(item, out count);

            if (count > 1)
                return false;

            dict[item] = count + 1;
        }

        return dict.All(kvp => kvp.Value == 2);
    }
}

答案 4 :(得分:0)

如果项目范围是0-31,则可以在uint32中存储32个一位标志。我建议采取每个项目并计算mask =(1 SHL项目),看看如果你尝试'或','xor'ing或添加掩码值会发生什么。查看有效和无效案例的结果。为避免溢出,您可能需要使用uint64进行添加(因为如果有两个31,或四个30或八个29,uint32可能会溢出。)

答案 5 :(得分:0)

我猜(从未测量过速度)这个codenipet可以给你一个新的观点:

int[] array = { 0, 1, 2, 3, 1, 1, 3, 5, 1, 2, 7, 31 }; // this is your sample array

uint[] powOf2 = {
    1, 2, 4, 8,
    16, 32, 64, 128,
    256, 512, 1024, 2048,
    4096, 8192, 16384, 32768,
    65536, 131072, 262144, 524288,
    1048576, 2097152, 4194304, 8388608,
    16777216, 33554432, 67108864, 134217728,
    268435456, 536870912, 1073741824, 2147483648
               };

uint now;
uint once = 0;
uint twice = 0;
uint more = 0;

for (int i = 0; i < array.Length; i++)
{
    now = powOf2[array[i]];

    more |= twice & now;
    twice ^= (once & now) & ~more;
    twice ^= more;
    once |= now;
}

您可以在变量“两次”中获得加倍值; 当然它只适用于小于32的值;