【问题标题】:Elegantly determine if more than one boolean is "true"优雅地确定多个布尔值是否为“真”
【发布时间】:2010-09-27 12:55:57
【问题描述】:

我有一组五个布尔值。如果其中一个以上是真的,我想执行一个特定的功能。你能想到的最优雅的方法是什么,它可以让我在一个 if() 语句中检查这个条件?目标语言是 C#,但我也对其他语言的解决方案感兴趣(只要我们不讨论特定的内置函数)。

一个有趣的选择是将布尔值存储在一个字节中,进行右移并与原始字节进行比较。像if(myByte && (myByte >> 1)) 这样的东西,但这需要将单独的布尔值转换为一个字节(通过 bitArray?),这似乎有点(双关语)笨拙...... [edit]对不起,应该是 if(myByte & (myByte - 1)) [/edit]

注意:这当然非常接近经典的“人口计数”、“横向加法”或“汉明权重”编程问题——但并不完全相同。我不需要知道设置了多少位,只要它不止一个。我希望有一种更简单的方法来实现这一点。

【问题讨论】:

  • (myByte && (myByte >> 1)) 有什么帮助?如果 myByte == 0x08,则仅设置一位,但表达式的计算结果为真。如果你的意思是 (myByte & (myByte >> 1)),那么当 myByte == 0x0a 时,例如,
  • 布尔或有问题?
  • 布尔值,如果至少有一个值为 True,则返回 True。如果 more than 一个值为 True,则 OP 想要返回 True 的东西。
  • @Michael Burr - 抱歉,应该是 (myByte & (myByte - 1)) 清除 LSB 并执行按位 AND 确定是否设置了多个位(如果 >1 则为假) .对总位数重复此操作,您可以获得设置位的计数。只是进行弹出计数的一种方法。
  • 比特数是一个有趣的谜题: public static int BitCount(int x) { return ((x == 0) ? 0 : ((x

标签: c# hammingweight


【解决方案1】:

我打算编写 Linq 版本,但有五个左右的人超过了我。但我真的很喜欢 params 方法,以避免手动新建一个数组。所以我认为最好的混合是,基于 rp 的回答,用明显的 Linqness 替换 body:

public static int Truth(params bool[] booleans)
{
    return booleans.Count(b => b);
}

阅读和使用都非常清晰:

if (Truth(m, n, o, p, q) > 2)

【讨论】:

  • 同意,出色的答案。如果有人正在寻找适合一般情况的解决方案,@DanielEarwicker 的回答启发了我:static int MatchCount<T>(Predicate<T> match, params T[] objs) { return objs.Count(obj => match(obj)); }。要计算正值的数量:double a, b, c, d; ... MatchCount(x => x > 0.0, a, b, c, d); 等等...
  • @AndersGustafsson 或者只是new[] { a, b, c, d }.Count(x => x > 0.0)
【解决方案2】:

怎么样

  if ((bool1? 1:0) + (bool2? 1:0) + (bool3? 1:0) + 
      (bool4? 1:0) + (bool5? 1:0) > 1)
      // do something

或者一个通用的方法是......

   public bool ExceedsThreshold(int threshold, IEnumerable<bool> bools)
    {
       int trueCnt = 0;
       foreach(bool b in bools)
          if (b && (++trueCnt > threshold)) 
              return true;
       return false;          
    } 

或按照其他答案的建议使用 LINQ:

    public bool ExceedsThreshold(int threshold, IEnumerable<bool> bools)
    { return bools.Count(b => b) > threshold; }

编辑(添加 Joel Coehoorn 建议: (在 .Net 2.x 及更高版本中)

    public void ExceedsThreshold<T>(int threshold, 
                      Action<T> action, T parameter, 
                      IEnumerable<bool> bools)
    { if (ExceedsThreshold(threshold, bools)) action(parameter); }

或在 .Net 3.5 及更高版本中:

    public void ExceedsThreshold(int threshold, 
            Action action, IEnumerable<bool> bools)
    { if (ExceedsThreshold(threshold, bools)) action(); }

或作为IEnumerable&lt;bool&gt;的扩展

  public static class IEnumerableExtensions
  {
      public static bool ExceedsThreshold<T> 
         (this IEnumerable<bool> bools, int threshold)
      { return bools.Count(b => b) > threshold; }
  }

然后用法是:

  var bools = new [] {true, true, false, false, false, false, true};
  if (bools.ExceedsThreshold(3))
      // code to execute  ...

【讨论】:

  • 要真正巧妙,有一个覆盖也接受一个动作。
  • 建议 - 一旦 b > 阈值就中断循环。
  • 六字母变量,第一个例子有什么问题。我认为这很简单,可以完成工作。
  • @Joel,你所说的“动作”是什么意思??如果条件为真,则代表要运行的方法?
  • 参数->参数。此外,您可以更改该函数以提前返回。
【解决方案3】:

现在是强制性 LINQ 答案的时候了,在这种情况下实际上非常简洁。

var bools = new[] { true, true, false, false, false };

return bools.Count(b => b == true) > 1;

【讨论】:

    【解决方案4】:

    我只会将它们转换为整数和总和。

    除非你处于一个超级紧密的内部循环中,否则它的好处是易于理解。

    【讨论】:

    • 有人必须添加这个的 linq 版本:myBools.Cast().Sum() !
    • @Jennifer 有点晚了(!)但遗憾的是Cast&lt;int&gt;().Sum() 会在一系列布尔值上抛出异常。虽然你可以投射bool -> int,但你不能投射bool -> object -> int,这就是这里幕后发生的事情。
    【解决方案5】:

    如果你的意思是大于或等于一个布尔值等于真,你可以这样做

    if (bool1 || bool2 || bool3 || bool4 || bool5)
    

    如果您需要多个(2 及以上)布尔值等于 true,您可以尝试

    int counter = 0;
    if (bool1) counter++;
    if (bool2) counter++;
    if (bool3) counter++;
    if (bool4) counter++;
    if (bool5) counter++;
    if (counter >= 2) //More than 1 boolean is true
    

    【讨论】:

      【解决方案6】:

      我会编写一个函数来接收任意数量的布尔值。它将返回那些为真的值的数量。检查结果,了解您需要积极的值的数量才能做某事。

      努力说清楚,而不是聪明!

      private int CountTrues( params bool[] booleans )
      {
          int result = 0;
          foreach ( bool b in booleans )
          {
              if ( b ) result++;
          }
      
          return result;
      }
      

      【讨论】:

        【解决方案7】:

        如果您的标志被打包成一个单词,那么Michael Burr's solution 将起作用。但是,循环不是必需的:

        int moreThanOneBitSet( unsigned int v)
        {
            return (v & (v - 1)) != 0;
        }
        

        例子

         v (binary) | v - 1 | v&(v-1) | result
        ------------+-------+---------+--------
               0000 |  1111 |    0000 |  false
               0001 |  0000 |    0000 |  false
               0010 |  0001 |    0000 |  false
               0011 |  0010 |    0010 |   true
               .... |  .... |    .... |   ....
               1000 |  0111 |    0000 |  false
               1001 |  1000 |    1000 |   true
               1010 |  1001 |    1000 |   true
               1011 |  1010 |    1010 |   true
               1100 |  1011 |    1000 |   true
               1101 |  1100 |    1100 |   true
               1110 |  1101 |    1100 |   true
               1111 |  1110 |    1110 |   true
        

        【讨论】:

          【解决方案8】:

          如果有数百万而不是只有 5 个,您可以避免使用 Count() 而是这样做...

          public static bool MoreThanOne (IEnumerable<bool> booleans)
          {
              return booleans.SkipWhile(b => !b).Skip(1).Any(b => b);
          }
          

          【讨论】:

          • 建议更简洁的版本:return booleans.Where(b =&gt; b).Skip(1).Any() 这也适用于我们想知道是否有超过 N 个成员满足某个条件的任何情况。
          【解决方案9】:

          比 Vilx-s 版本更短更丑:

          if (((a||b||c)&&(d||e))||((a||d)&&(b||c||e))||(b&&c)) {}
          

          【讨论】:

          • 是的,它很可怕,但它有效(我已经验证了所有组合)。
          【解决方案10】:

          从我的脑海中,这个特定示例的快速方法;您可以将 bool 转换为 int(0 或 1)。然后循环遍历它们并将它们相加。如果结果 >= 2 那么你可以执行你的函数。

          【讨论】:

            【解决方案11】:

            虽然我喜欢 LINQ,但它有一些漏洞,比如这个问题。

            一般来说,计数是可以的,但当您计数的项目需要一段时间来计算/检索时,这可能会成为一个问题。

            如果您只想检查任何内容,Any() 扩展方法很好,但如果您想检查至少没有内置函数可以做到这一点并且是懒惰的。

            最后,我写了一个函数,如果列表中至少有一定数量的项目,则返回 true。

            public static bool AtLeast<T>(this IEnumerable<T> source, int number)
            {
                if (source == null)
                    throw new ArgumentNullException("source");
            
                int count = 0;
                using (IEnumerator<T> data = source.GetEnumerator())
                    while (count < number && data.MoveNext())
                    {
                        count++;
                    }
                return count == number;
            }
            

            使用方法:

            var query = bools.Where(b => b).AtLeast(2);
            

            这样做的好处是不需要在返回结果之前评估所有项目。

            [Plug] 我的项目NExtension 包含 AtLeast、AtMost 和覆盖,允许您将谓词与 AtLeast/Most 检查混合。 [/插头]

            【讨论】:

              【解决方案12】:

              转换为整数和求和应该可以,但它有点难看,在某些语言中可能是不可能的。

              这样的事情怎么样

              int count = (bool1? 1:0) + (bool2? 1:0) + (bool3? 1:0) + (bool4? 1:0) + (bool5? 1:0);
              

              或者,如果您不关心空间,您可以预先计算真值表并将布尔值用作索引:

              if (morethanone[bool1][bool2][bool3][bool4][bool5]) {
               ... do something ...
              }
              

              【讨论】:

              • 是的,我也是。哈哈。在某些情况下,预计算有助于提高性能 - 尽管我认为这不是其中之一 :-)
              【解决方案13】:

              我会做这样的事情,使用 params 参数。

                      public void YourFunction()
                      {
                          if(AtLeast2AreTrue(b1, b2, b3, b4, b5))
                          {
                              // do stuff
                          }
                      }
              
                      private bool AtLeast2AreTrue(params bool[] values)
                      {
                          int trueCount = 0;
                          for(int index = 0; index < values.Length || trueCount >= 2; index++)
                          {
                              if(values[index])
                                  trueCount++;
                          }
              
                          return trueCount > 2;
              
                      }
              

              【讨论】:

              • 您可以将 if 语句简化为:return trueCount >= 2
              • 另外,我不确定这是否特别符合“优雅”的定义
              • @frankodwyer 没错,我改了。我认为它可能与真假一起更具可读性,但再看一遍肯定更好。
              【解决方案14】:
              if (NumberOfTrue(new List<bool> { bool1, bool2, bool3, bool4 }) >= 2)
              {
                  // do stuff
              }
              
              int NumberOfTrue(IEnumerable<bool> bools)
              {
                  return bools.Count(b => b);
              }
              

              【讨论】:

                【解决方案15】:

                不完全漂亮...但这是另一种方法:

                if (
                    (a && (b || c || d || e)) ||
                    (b && (c || d || e)) ||
                    (c && (d || e)) ||
                    (d && e)
                )
                

                【讨论】:

                • 怎么样 (a && (b || c || d || e)) || (b & ( c || d || 3)) || (c && (d || e)) || (d && e) ?
                • 很好......我可以用这个作为面试问题......“这是做什么的?”衡量纯粹的智力,以及“你认为它是什么解决方案?”淘汰任何喜欢它的人。
                • 哇!谈论蛮力和该死的无知! :-)
                【解决方案16】:

                我现在有一个好多了,而且很短!

                bool[] bools = { b1, b2, b3, b4, b5 };
                if (bools.Where(x => x).Count() > 1)
                {
                   //do stuff
                }
                

                【讨论】:

                • 有些人已经写过那个了,尽管谓词传递给了 Count 而不需要 Where。
                【解决方案17】:

                我想给出一个 C++11 可变参数模板的答案。

                template< typename T>
                T countBool(T v)
                {
                    return v;
                }
                
                template< typename T, typename... Args>
                int countBool(T first, Args... args)
                {
                    int boolCount = 0;
                    if ( first )
                        boolCount++;
                    boolCount += countBool( args... );
                    return boolCount;
                }
                

                简单地如下调用它会创建一个相当优雅的计算布尔数的方法。

                if ( countBool( bool1, bool2, bool3 ) > 1 )
                {
                  ....
                }
                

                【讨论】:

                  【解决方案18】:

                  在大多数语言中,true 等价于非零值,而 false 为零。我没有确切的语法给你,但是在伪代码中,怎么样:

                  if ((bool1 * 1) + (bool2 * 1) + (bool3 * 1) > 2)
                  {
                      //statements here
                  }
                  

                  【讨论】:

                  • 在 true 等于任何非零值的任何语言中均无效。 (该示例仅在 1 始终用于 true 时才有效。)
                  【解决方案19】:

                  如果您只有五个不同的值,您可以轻松地进行测试,方法是将这些位打包成一个 short 或一个 int 并检查它是否是零或一位答案。你能得到的唯一无效数字是..

                  0x 0000 0000 0x 0000 0001 0x 0000 0010 0x 0000 0100 0x 0000 1000 0x 0001 0000

                  这为您提供了六个要搜索的值,将它们放入查找表中,如果不存在,您就有答案了。

                  这给了你一个简单的答案。

                  公共静态布尔 moreThan1BitSet(int b) { 最终的短 multiBitLookup[] = { 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 如果(多位查找 [b] == 1) 返回假; 返回真; }

                  这不能很好地扩展到超过 8 位,但您只有 5 个。

                  【讨论】:

                    【解决方案20】:

                    如果((b1.CompareTo(假)+ b2.CompareTo(假)+ b3.CompareTo(假)+ ...)> 1)

                    // 多于一个是真的

                    ...

                    其他

                    ...

                    【讨论】:

                      【解决方案21】:

                      你提到了

                      一个有趣的选择是将布尔值存储在一个字节中, 进行右移并与原始字节进行比较。 类似if (myByte &amp;&amp; (myByte &gt;&gt; 1))

                      我不认为该表达式会给您想要的结果(至少使用 C 语义,因为该表达式不是有效的 C#):

                      如果是(myByte == 0x08),那么即使只设置了一位,表达式也会返回真。

                      如果您的意思是“if (myByte &amp; (myByte &gt;&gt; 1))”,那么如果 (myByte == 0x0a) 表达式将返回 false,即使设置了 2 位。

                      但这里有一些计算单词位数的技巧:

                      Bit Twiddling Hacks - Counting bits

                      您可能考虑的一种变体是使用 Kernighan 的计数方法,但请尽早退出,因为您只需要知道是否设置了多个位:

                      int moreThanOneBitSet( unsigned int v)
                      {
                          unsigned int c; // c accumulates the total bits set in v
                      
                          for (c = 0; v && (c <= 1); c++)
                          {
                            v &= v - 1; // clear the least significant bit set
                          }
                      
                          return (c > 1);
                      }
                      

                      当然,使用查找表也不错。

                      【讨论】:

                        【解决方案22】:

                        以性能为导向的解决方案

                        因为以下陈述在 .NET 中是正确的

                        • sizeof(bool) == 1
                        • *(byte*)&amp;someBool == 1 其中 someBool 是 true
                        • *(byte*)&amp;someBool == 0 其中 someBool 是 false

                        您可以退回到 unsafe 代码和指针转换(因为 C# 不允许简单地将 bool 转换为 byteint)。

                        您的代码将如下所示

                        if (*(byte*)&bool1 + *(byte*)&bool2 + *(byte*)&bool3 > 1)
                        {
                            // do stuff
                        }
                        

                        这里的好处是你没有任何额外的分支,这使得这个分支比明显的myBool ? 1 : 0 更快。 这里的缺点是使用unsafe 和指针,这在托管.NET 世界中通常不是一个广受欢迎的解决方案。此外,sizeof(bool) == 1 的假设可能会受到质疑,因为这并不适用于所有语言,但至少在 C# .NET 中它是正确的。

                        如果指针的东西对你来说太烦人了,你可以随时将它隐藏在扩展方法中:

                        using System.Runtime.CompilerServices;
                        
                        // ...
                        
                        [MethodImpl(MethodImplOptions.AggressiveInlining)]
                        public static unsafe int ToInt(this bool b) => *(byte*)&b;
                        

                        你的代码会变得更易读

                        if (bool1.ToInt() + bool2.ToInt() + bool3.ToInt() > 1)
                        {
                            // do stuff
                        }
                        

                        显然,您可以随时将其与 LINQ 结合使用

                        if (myBools.Sum(b => b.ToInt()) > 1)
                        {
                            // do stuff
                        }
                        

                        或者如果你看重性能而不是其他任何东西,这个可能更快

                        bool[] myBools = ...
                        
                        fixed (bool* boolPtr = myBools)
                        {
                            byte* bytePtr = (byte*)boolPtr;
                            int numberOfTrueBools = 0;
                        
                            // count all true booleans in the array
                            for (int i = 0; i < myBools.Length; numberOfTrueBools += bytePtr[i], i++);
                        
                            // do something with your numberOfTrueBools ...
                        }
                        

                        或者,如果您有一个 巨大的 输入数组,您甚至可以选择硬件加速 SIMD 解决方案...

                        using System.Runtime.CompilerServices;
                        using System.Runtime.Intrinsics;
                        using System.Runtime.Intrinsics.X86;
                        
                        // ...
                        
                        [MethodImpl(MethodImplOptions.AggressiveOptimization)]
                        public static unsafe int CountTrueBytesSIMD(this bool[] myBools)
                        {
                            // we need to get a pointer to the bool array to do our magic
                            fixed (bool* ptr = myBools)
                            {
                                // reinterpret all booleans as bytes
                                byte* bytePtr = (byte*)ptr;
                        
                                // calculate the number of 32 bit integers that would fit into the array
                                int dwordLength = myBools.Length >> 2;
                                
                                // for SIMD, allocate a result vector
                                Vector128<int> result = Vector128<int>.Zero;
                                
                                // loop variable
                                int i = 0;
                                
                                // it could be that SSSE3 isn't supported...
                                if (Ssse3.IsSupported)
                                {
                                    // remember: we're assuming little endian!
                                    // we need this mask to convert the byte vectors to valid int vectors
                                    Vector128<int> cleanupMask = Vector128.Create(0x000000FF);
                                    
                                    // iterate over the array processing 16 bytes at once
                                    // TODO: you could even go to 32 byte chunks if AVX-2 is supported...
                                    for (; i < dwordLength - Vector128<int>.Count; i += Vector128<int>.Count)
                                    {
                                        // load 16 bools / bytes from memory
                                        Vector128<byte> v = Sse2.LoadVector128((byte*)((int*)bytePtr + i));
                        
                                        // now count the number of "true" bytes in every 32 bit integers
                                        // 1. shift
                                        Vector128<int> v0 = v.As<byte, int>();
                                        Vector128<int> v1 = Sse2.ShiftRightLogical128BitLane(v, 1).As<byte, int>();
                                        Vector128<int> v2 = Sse2.ShiftRightLogical128BitLane(v, 2).As<byte, int>();
                                        Vector128<int> v3 = Sse2.ShiftRightLogical128BitLane(v, 3).As<byte, int>();
                        
                                        // 2. cleanup invalid bytes
                                        v0 = Sse2.And(v0, cleanupMask);
                                        v1 = Sse2.And(v1, cleanupMask);
                                        v2 = Sse2.And(v2, cleanupMask);
                                        v3 = Sse2.And(v3, cleanupMask);
                        
                                        // 3. add them together. We now have a vector of ints holding the number
                                        // of "true" booleans / 0x01 bytes in their 32 bit memory region
                                        Vector128<int> roundResult = Sse2.Add(Sse2.Add(Sse2.Add(v0, v1), v2), v3);
                        
                                        // 4 now add everything to the result
                                        result = Sse2.Add(result, roundResult);
                                    }
                        
                                    // reduce the result vector to a scalar by horizontally adding log_2(n) times
                                    // where n is the number of words in out vector
                                    result = Ssse3.HorizontalAdd(result, result);
                                    result = Ssse3.HorizontalAdd(result, result);
                                }
                                int totalNumberOfTrueBools = result.ToScalar();
                        
                                // now add all remaining booleans together 
                                // (if the input array wasn't a multiple of 16 bytes or SSSE3 wasn't supported)
                                i <<= 2;
                                for (; i < myBools.Length; totalNumberOfTrueBools += bytePtr[i], i++);
                                return totalNumberOfTrueBools;
                            }
                        }
                        

                        【讨论】:

                          【解决方案23】:

                          我最近遇到了同样的问题,我有三个布尔值,我需要检查一次只有其中一个是正确的。为此,我使用了 xor 运算符,如下所示:

                          bool a = true;
                          bool b = true;
                          bool c = false;
                          
                          if (a || b || c)
                          {
                              if (a ^ b ^ c){
                                  //Throw Error
                              }
                          }
                          

                          此代码将抛出错误,因为 a 和 b 都为真。

                          供参考:http://www.dotnetperls.com/xor

                          我刚刚在 C# 中找到了 xor 运算符,如果有人知道此策略的任何坑,请告诉我。

                          【讨论】:

                            猜你喜欢
                            • 2021-12-04
                            • 2019-11-03
                            • 2012-01-05
                            • 1970-01-01
                            • 2017-03-06
                            • 2013-09-18
                            • 2013-01-31
                            • 2019-04-07
                            • 2012-01-06
                            相关资源
                            最近更新 更多