83

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

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

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

4

23 回答 23

122

我本来打算写 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)
于 2008-12-18T15:53:56.407 回答
95

怎么样

  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<bool>

  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  ...
于 2008-12-18T14:37:00.743 回答
22

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

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

return bools.Count(b => b == true) > 1;
于 2008-12-18T15:22:19.870 回答
16

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

除非您处于超级紧密的内部循环中,否则这样做的好处是易于理解。

于 2008-12-18T14:34:27.567 回答
7

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

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
于 2008-12-18T14:35:39.730 回答
6

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

努力说清楚,不聪明!

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

    return result;
}
于 2008-12-18T15:01:28.743 回答
6

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

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
于 2011-05-10T15:45:27.320 回答
5

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

public static bool MoreThanOne (IEnumerable<bool> booleans)
{
    return booleans.SkipWhile(b => !b).Skip(1).Any(b => b);
}
于 2010-06-06T03:21:55.987 回答
4

比 Vilx-s 版本更短更丑:

if (((a||b||c)&&(d||e))||((a||d)&&(b||c||e))||(b&&c)) {}
于 2008-12-18T17:43:19.930 回答
2

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

于 2008-12-18T14:36:04.643 回答
2

虽然我喜欢 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 检查混合。[/插头]

于 2008-12-19T15:08:42.797 回答
1

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

像这样的东西怎么样

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 ...
}
于 2008-12-18T14:43:43.473 回答
1

我会做这样的事情,使用 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;

        }
于 2008-12-18T14:48:31.610 回答
1

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

if (
    (a && (b || c || d || e)) ||
    (b && (c || d || e)) ||
    (c && (d || e)) ||
    (d && e)
)
于 2008-12-18T14:48:33.777 回答
1
if (NumberOfTrue(new List<bool> { bool1, bool2, bool3, bool4 }) >= 2)
{
    // do stuff
}

int NumberOfTrue(IEnumerable<bool> bools)
{
    return bools.Count(b => b);
}
于 2008-12-18T15:04:41.000 回答
1

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

bool[] bools = { b1, b2, b3, b4, b5 };
if (bools.Where(x => x).Count() > 1)
{
   //do stuff
}
于 2008-12-18T15:34:46.273 回答
1

我想给出一个 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 )
{
  ....
}
于 2017-06-10T20:30:55.600 回答
0

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

if ((bool1 * 1) + (bool2 * 1) + (bool3 * 1) > 2)
{
    //statements here
}
于 2008-12-18T14:36:09.247 回答
0

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

// 多于一个是真的

...

别的

...

于 2008-12-18T18:19:36.457 回答
0

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

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 位。

于 2008-12-18T18:59:56.047 回答
0

你提到

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

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

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

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

但这里有一些计算字中位数的技术:

Bit Twiddling Hacks - 数位

您可能考虑的一种变体是使用 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);
}

当然,使用查找表也不是一个坏选择。

于 2008-12-19T02:20:48.013 回答
0

以性能为导向的解决方案

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

  • sizeof(bool) == 1
  • *(byte*)&someBool == 1someBool 在哪里true
  • *(byte*)&someBool == 0someBool 在哪里false

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

您的代码将看起来像这样

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;
    }
}
于 2022-01-13T14:08:24.993 回答
-1

我最近遇到了同样的问题,我有三个布尔值,我需要检查一次只有其中一个是真的。为此,我使用了 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 运算符,如果有人知道这种策略的任何坑,请告诉我。

于 2013-12-24T19:26:15.520 回答