c# - Elegantemente determine si más de un booleano es "verdadero"
hammingweight (22)
¡Ahora tengo uno mucho mejor y muy corto!
bool[] bools = { b1, b2, b3, b4, b5 };
if (bools.Where(x => x).Count() > 1)
{
//do stuff
}
Tengo un conjunto de cinco valores booleanos. Si más de uno de estos son verdaderos, quiero superar una función en particular. ¿Cuál es la forma más elegante en que se puede pensar que me permitiría verificar esta condición en una sola declaración if ()? El idioma de destino es C #, pero también estoy interesado en soluciones en otros idiomas (siempre que no hablemos de funciones integradas específicas).
Una opción interesante es almacenar los booleanos en un byte, hacer un cambio a la derecha y compararlos con el byte original. Algo como if(myByte && (myByte >> 1))
Pero esto requeriría convertir los booleanos separados a un byte (a través de un bitArray?) Y eso parece un poco (juego de palabras) torpe ... [edit] Lo siento, eso debería han sido if(myByte & (myByte - 1))
[/ edit]
Nota: Esto, por supuesto, está muy cerca del clásico problema de "recuento de población", "adición lateral" o "peso de Hamming", pero no es lo mismo. No necesito saber cuántos de los bits están configurados, solo si es más de uno. Mi esperanza es que haya una manera mucho más simple de lograr esto.
Aunque me gusta LINQ, hay algunos agujeros, como este problema.
Hacer un conteo está bien en general, pero puede convertirse en un problema cuando los elementos que su conteo toman un tiempo para calcular / recuperar.
El método de extensión Any () está bien si solo desea buscar alguno, pero si quiere verificar al menos no hay una función incorporada que lo haga y sea flojo.
Al final, escribí una función para devolver verdadero si hay al menos un cierto número de elementos en la lista.
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;
}
Usar:
var query = bools.Where(b => b).AtLeast(2);
Esto tiene el beneficio de no tener que evaluar todos los artículos antes de devolver un resultado.
[Plug] Mi proyecto, NExtension contiene AtLeast, AtMost y reemplaza que le permiten mezclar el predicado con la comprobación AtLeast / Most. [/Enchufe]
Casting a ints y summing debería funcionar, pero es un poco feo y en algunos idiomas puede no ser posible.
¿Qué tal algo así como
int count = (bool1? 1:0) + (bool2? 1:0) + (bool3? 1:0) + (bool4? 1:0) + (bool5? 1:0);
O si no te importa el espacio, puedes calcular previamente la tabla de verdad y usar los bools como índices:
if (morethanone[bool1][bool2][bool3][bool4][bool5]) {
... do something ...
}
En la mayoría de los idiomas, verdadero es equivalente a un valor distinto de cero, mientras que falso es cero. No tengo una sintaxis exacta para ti, pero en pseudo código, ¿qué hay de:
if ((bool1 * 1) + (bool2 * 1) + (bool3 * 1) > 2)
{
//statements here
}
Es hora de la respuesta LINQ obligatoria, que en este caso es bastante clara.
var bools = new[] { true, true, false, false, false };
return bools.Count(b => b == true) > 1;
Escribiría una función para recibir cualquier cantidad de valores booleanos. Devolvería el número de esos valores que son verdaderos. Verifique el resultado de la cantidad de valores que necesita para ser positivo para hacer algo.
¡Trabaja más duro para dejarlo claro, no inteligente!
private int CountTrues( params bool[] booleans )
{
int result = 0;
foreach ( bool b in booleans )
{
if ( b ) result++;
}
return result;
}
Haría algo así, usando el argumento de 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;
}
Iba a escribir la versión de Linq, pero unas cinco personas me ganaron. Pero realmente me gusta el enfoque de params para evitar tener que actualizar manualmente una matriz. Así que creo que el mejor híbrido es, basado en la respuesta de rp con el cuerpo reemplazar con el obvio Linqness:
public static int Truth(params bool[] booleans)
{
return booleans.Count(b => b);
}
Maravillosamente claro para leer y usar:
if (Truth(m, n, o, p, q) > 2)
Más corto y más feo que la versión de Vilx-s:
if (((a||b||c)&&(d||e))||((a||d)&&(b||c||e))||(b&&c)) {}
Mencionaste
Una opción interesante es almacenar los booleanos en un byte, hacer un cambio a la derecha y compararlos con el byte original. Algo como
if (myByte && (myByte >> 1))
No creo que esa expresión te proporcione el resultado que deseas (al menos usando la semántica C, ya que la expresión no es válida C #):
Si (myByte == 0x08)
, la expresión volverá a ser verdadera aunque solo haya un bit establecido.
Si se refería a " if (myByte & (myByte >> 1))
", entonces if (myByte == 0x0a)
la expresión devolverá false aunque haya 2 bits configurados.
Pero aquí hay algunas técnicas para contar la cantidad de bits en una palabra:
Bit Twiddling Hacks - Contando bits
Una variante que podría considerar es usar el método de conteo de Kernighan, pero rescatar temprano, ya que solo necesita saber si hay más de un bit configurado:
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);
}
Por supuesto, usar una tabla de búsqueda tampoco es una mala opción.
No es exactamente bonito ... pero esta es otra forma de hacerlo:
if (
(a && (b || c || d || e)) ||
(b && (c || d || e)) ||
(c && (d || e)) ||
(d && e)
)
Qué tal si
if ((bool1? 1:0) + (bool2? 1:0) + (bool3? 1:0) +
(bool4? 1:0) + (bool5? 1:0) > 1)
// do something
o un método generalizado sería ...
public bool ExceedsThreshold(int threshold, IEnumerable<bool> bools)
{
int trueCnt = 0;
foreach(bool b in bools)
if (b && (++trueCnt > threshold))
return true;
return false;
}
o usando LINQ como lo sugieren otras respuestas:
public bool ExceedsThreshold(int threshold, IEnumerable<bool> bools)
{ return bools.Count(b => b) > threshold; }
EDITAR (para agregar la sugerencia de Joel Coehoorn: (en .Net 2.xy posterior)
public void ExceedsThreshold<T>(int threshold,
Action<T> action, T parameter,
IEnumerable<bool> bools)
{ if (ExceedsThreshold(threshold, bools)) action(parameter); }
o en .Net 3.5 y posterior:
public void ExceedsThreshold(int threshold,
Action action, IEnumerable<bool> bools)
{ if (ExceedsThreshold(threshold, bools)) action(); }
o como una extensión de IEnumerable<bool>
public static class IEnumerableExtensions
{
public static bool ExceedsThreshold<T>
(this IEnumerable<bool> bools, int threshold)
{ return bools.Count(b => b) > threshold; }
}
el uso sería entonces:
var bools = new [] {true, true, false, false, false, false, true};
if (bools.ExceedsThreshold(3))
// code to execute ...
Quería dar una respuesta de plantilla variadica de 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;
}
simplemente llamarlo de la siguiente manera crea un método bastante elegante para contar el número de bools.
if ( countBool( bool1, bool2, bool3 ) > 1 )
{
....
}
Recientemente tuve este mismo problema, donde tenía tres valores booleanos, que necesitaba comprobar que solo uno de ellos era verdadero a la vez. Para esto utilicé el operador xor de la siguiente manera:
bool a = true;
bool b = true;
bool c = false;
if (a || b || c)
{
if (a ^ b ^ c){
//Throw Error
}
}
Este código arrojará un error ya que a y b son ambos verdaderos.
Para referencia: http://www.dotnetperls.com/xor
Acabo de encontrar el operador xor en C # si alguien sabe de alguna caída de esta estrategia, por favor avíseme.
Si hubiera millones en lugar de solo 5, podría evitar Count () y hacer esto en su lugar ...
public static bool MoreThanOne (IEnumerable<bool> booleans)
{
return booleans.SkipWhile(b => !b).Skip(1).Any(b => b);
}
Si solo tiene cinco valores diferentes, puede hacer la prueba fácilmente colocando los bits en un breve o en un int y comprobando si es una de las respuestas de cero o de un bit. Los únicos números inválidos que podría obtener serían ...
0x 0000 0000 0x 0000 0001 0x 0000 0010 0x 0000 0100 0x 0000 1000 0x 0001 0000
Esto le da seis valores para buscar, ponerlos en una tabla de búsqueda y si no está ahí, tiene su respuesta.
Esto te da una respuesta simple.
public static boolean moreThan1BitSet(int b) { final short 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 }; if(multiBitLookup[b] == 1) return false; return true; }
Esto no escala mucho más allá de 8 bits, pero solo tienes cinco.
Si sus banderas están empaquetadas en una sola palabra, la solución de Michael Burr funcionará. Sin embargo, el ciclo no es necesario:
int moreThanOneBitSet( unsigned int v)
{
return (v & (v - 1)) != 0;
}
ejemplo
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
Simplemente los convertiría en enteros y suma.
A menos que esté en un bucle interno súper apretado, tiene la ventaja de ser fácil de entender.
desde lo más alto de mi cabeza, un enfoque rápido para este ejemplo específico; podrías convertir el bool a un int (0 o 1). luego recorre el therm y agrégalo. si el resultado es> = 2, entonces puede ejecutar su función.
if ((b1.CompareTo (falso) + b2.CompareTo (falso) + b3.CompareTo (falso) + ...)> 1)
// Más de uno de ellos son verdaderos
...
más
...
si quiere decir más o igual a un booleano igual a verdadero, podría hacerlo como
if (bool1 || bool2 || bool3 || bool4 || bool5)
Si necesita más de un (2 y más) booleanos igual a verdadero, puede intentar
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
if (NumberOfTrue(new List<bool> { bool1, bool2, bool3, bool4 }) >= 2)
{
// do stuff
}
int NumberOfTrue(IEnumerable<bool> bools)
{
return bools.Count(b => b);
}