【问题标题】:How can I quickly tell if a list contains only duplicates?如何快速判断列表是否仅包含重复项?
【发布时间】:2011-05-10 07:16:54
【问题描述】:

有多个相关问题,但我正在寻找针对我的案例的解决方案。有一个(通常)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 出现 2n 次且 n > 1。在这种情况下,检查应该 失败,即应该有 7 个不同的对.

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

【问题讨论】:

  • 数字数组排序好了吗?您应该告诉我们数组是否有什么特别之处,这可能有助于改进解决方案。
  • 我目前正在分析答案以确定谁将获得 +15。
  • @Danny 数组未排序。除了我到目前为止所说的之外,我想不出任何有用的东西。

标签: c# .net list duplicates mahjong


【解决方案1】:

好吧,鉴于您的确切要求,我们可以更聪明一点。像这样的:

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。看起来一点也不像 LINQ 变体那样优雅,它是一种易于阅读的单行代码,但这应该会更快一些,因为一旦找到三个相等的元素就会跳出来。
  • +1:很棒的东西,没想到这个;它避免了随后通过 counts 数组。
  • 或者,你可以保持pairsCount,最后保持return pairsCount*2 == array.Length; 当然保持找到3-of-a-kind的早期回报。
  • 数组“(通常)”有 14 个元素。如果元素数量为奇数if(array.Length % 2 != 0) return false;,您可能会提前退出
  • @Binary Worrier:好电话,将添加。
【解决方案2】:

显然,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 指出的错误。我还应该指出,他的算法更智能,可能更快。

【讨论】:

  • +1:我只是在写这个确切的解决方案,但显然有点晚了;)
  • 还不是 -1,但只有在有 64 个值时才会返回 true……你的 seen != 2 应该是 seen != 0 && seen != 2。或者,只需检查seen == 1
  • @Jon Skeet:谢谢你,乔恩。我犯了一个需要==2的LINQ解决方案欺骗的错误。
【解决方案3】:

我将创建一个由 32 个整数元素组成的数组,初始化为零。我们就叫它“比利”吧。

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

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

【讨论】:

    【解决方案4】:

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

    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);
        }
    }
    

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 其实我原来的描述写错了。取值范围是 1 到 34,所以无论如何都需要 uint64。
      • @Barna 实现了这一点,请参阅我的评论。
      • @mafutrct:有一种更简单的方法来查找两次而不是两次以上。查看算术和和“或”值。注意所有有效情况下的任何关系,这不适用于任何无效情况?请注意,在无效案例中没有一致的“错误”模式,但有效案例与任何无效案例都有一些不同。
      • 我看到存在相关性,但我不确定使用它在数学上是否正确?您能否添加一个解释,或者添加一个代码 sn-p,以便我可以清楚地理解您的想法?
      • @mafutrct: 嗯...如果 OR 值至少设置了 7 位,那么总和可以是两倍的唯一方法是,如果每个位恰好出现两次(并且正好设置了 7 位) )。不过,检查是否至少设置了 7 位可能有点烦人。如果没有检查,您可能会得到误报,例如4,1,1,1,1,1,1(OR's to 5 and ADDs to 10)
      【解决方案6】:

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

      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 的值;

      【讨论】:

      • 啊,我发帖后读到:您将值范围更正为 34。无论如何,您仍然可以使用 uint64。我想这个解决方案比目前提出的其他解决方案要快得多。很抱歉帖子的注意事项不好,没有发帖经验。
      • 我还没有测试过这个,但是因为它包含比其他想法更多的操作,所以应该更慢,不是吗?
      • 我已经测试过了。 1) 取决于输入数组。由于问题是所有都是对的,因此 Ani 或 Jon Skeet 的答案可能更快,而在第一次出现三次时给出结果。 2) 对于包含数字 1..31 的输入数组,将每个选择循环一百万次,通过对百万循环前后的时间进行采样获得以下结果:Barna - 差异:0.4220000 s Ani - 差异:0.4460000 s Jon Skeet -差异:0.4850000 s 3)无论如何,我的仍然包含单峰,双峰和多峰完全作为一个频谱。好的,它不在规范中。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-01
      • 2014-10-10
      • 1970-01-01
      • 2018-06-20
      • 2020-07-21
      • 2018-02-25
      • 2014-07-28
      相关资源
      最近更新 更多