Si la condición A coincide, la condición B debe coincidir para realizar la acción C

148

Mi pregunta es:

if (/* condition A */)
{
    if(/* condition B */)
      {
         /* do action C */
      }
    else
      /* ... */
}
else
{
   /* do action C */
}

¿Es posible simplemente escribir el código de acción C una vez en lugar de dos?

¿Cómo simplificarlo?

starf15h
fuente
56
¿Poner el código para "acción C" en una función?
CinCout
26
Esto es triste, esto no está realmente relacionado con la pregunta de C ++ llegó a HNQ: /
YSC
2
¡Gracias a todos por ayudarme! Al principio, solo quiero asegurarme de que todo esté bien, así que usé un if anidado. Es porque esta es la forma más simple que supuse. Intentaré poner más esfuerzo después de hacer preguntas la próxima vez. Deseo que todos tengan un buen día :)
starf15h
13
Esa es una muy buena estrategia: escribir código que funcione primero, luego preocuparse por hacerlo elegante y eficiente más tarde.
Code-Apprentice
3
@Tim Lo publiqué como respuesta. En una nota al margen, es triste ver menos votos allí.
CinCout

Respuestas:

400

Su primer paso en este tipo de problemas es siempre hacer una tabla lógica.

A | B | Result
-------------------
T | T | do action C
T | F | ...
F | T | do action C
F | F | do action C

Una vez que haya hecho la tabla, la solución es clara.

if (A && !B) {
  ...
}
else {
  do action C
}

Tenga en cuenta que esta lógica, aunque más corta, puede ser difícil de mantener para futuros programadores.

Pregunta C
fuente
35
Realmente me gusta que haya mostrado la tabla de verdad para ayudar al OP a comprender cómo desarrollar esto él mismo. ¿Puedes dar un paso más y explicar cómo obtienes la expresión booleana de la tabla de verdad? Para alguien nuevo en programación y lógica booleana, esto probablemente no esté del todo claro.
Code-Apprentice
14
Si la evaluación Btiene efectos secundarios, la tabla lógica tiene que dar cuenta de eso.
Yakk - Adam Nevraumont
79
@Yakk Mi respuesta no aborda los efectos secundarios por dos razones. Primero, la solución tiene (casualmente) el comportamiento correcto de efectos secundarios. En segundo lugar, y lo que es más importante, que A y B tengan efectos secundarios sería un código incorrecto y una discusión sobre ese caso marginal sería una distracción para una pregunta fundamental sobre la lógica booleana.
QuestionC
52
Quizás valga la pena señalar, en caso de que el A && !Bcaso sea un no-op: !(A && !B)es equivalente a lo !A || Bque significa que puede hacer if (!A || B) { /* do action C */ }y evitar un bloque vacío.
KRyan
54
Si if (A && !B)es realmente difícil de mantener para los futuros programadores, entonces realmente no hay forma de ayudarlos.
Ray
65

Tienes dos opciones:

  1. Escriba una función que realice la "acción C".

  2. Reorganice su lógica para que no tenga tantas sentencias if anidadas. Pregúntese qué condiciones causan la "acción C". Me parece que sucede cuando la "condición B" es verdadera o la "condición A" es falsa. Podemos escribir esto como "NO A O B". Traduciendo esto al código C, obtenemos

    if (!A || B) {
        action C
    } else {
        ...
    }
    

Para obtener más información sobre este tipo de expresiones, sugiero buscar en Google "álgebra booleana", "lógica de predicados" y "cálculo de predicados". Estos son temas matemáticos profundos. No necesitas aprenderlo todo, solo lo básico.

También debe aprender sobre la "evaluación de cortocircuito". Debido a esto, el orden de las expresiones es importante para duplicar exactamente su lógica original. Si bien B || !Aes lógicamente equivalente, usar esto como condición ejecutará la "acción C" cuando Bsea ​​verdadera, independientemente del valor de A.

Aprendiz de código
fuente
15
@Yakk Vea las leyes de deMorgan.
Code-Apprentice
@ Code-Apprentice Por favor, perdona mi mal pensamiento lógico. Me gustaría preguntar si hay alguna diferencia entre (! A || B) y (A &&! B). Parece que ambos están bien para mi problema. Me refiero al enfoque suyo y de QuestionC.
starf15h
66
@ Starf15h Hay otra diferencia crucial: dónde se ejecuta la "acción C". Esta diferencia hace que nuestras dos soluciones sean exactamente equivalentes. Te sugiero que busques en Google las "Leyes de deMorgan" que deberían ayudarte a entender lo que está sucediendo aquí.
Code-Apprentice el
55
Las dos soluciones son exactamente equivalentes, pero puede haber una diferencia práctica dependiendo de lo que ...sea exactamente . Si no es nada en absoluto (es decir, "haga C si se cumplen estas condiciones; de lo contrario no haga nada"), entonces esta es claramente la solución superior, ya que la elsedeclaración simplemente puede omitirse por completo en ese momento.
Janus Bahs Jacquet
1
Además, dependiendo de los nombres de A y B, esta disposición puede ser más legible o menos legible para un humano que la disposición de QuestionC.
Michael - ¿Dónde está Clay Shirky?
15

Puede simplificar la declaración de esta manera:

if ((A && B) || (!A)) // or simplified to (!A || B) as suggested in comments
{
    do C
}

De lo contrario, coloque el código para 'C' en una función separada y llámelo:

DoActionC()
{
    ....
    // code for Action C
}
if (condition A)
{
    if(condition B)
      {
         DoActionC(); // call the function
      }
    else
      ...
}
else
{
   DoActionC(); // call the function
}
CinCout
fuente
77
O más simplementeif (!A || B)
Tas
2
Lógicamente, ((A && B) ||! A) es equivalente a (B ||! A)
Code-Apprentice
@ Code-Apprentice B || !Aresultará truesolo si Bes así true, sin verificar realmente Adebido a un cortocircuito
CinCout
1
@CinCout Buen punto. Si bien mi afirmación sigue siendo cierta desde una perspectiva teórica de lógica booleana, no tomé en cuenta los aspectos prácticos de los operadores booleanos de corto circuito. Afortunadamente, mi propia respuesta tiene el orden correcto.
Code-Apprentice
1
Entonces, desde una perspectiva lógica, el orden no importa. Sin embargo, desde un punto de vista de mantenimiento y legibilidad, puede haber una gran diferencia dependiendo de qué es exactamente Ay qué Bsignifica.
Code-Apprentice
14

En un lenguaje con coincidencia de patrones, puede expresar la solución de una manera que refleje más directamente la tabla de verdad en la respuesta de QuestionC.

match (a,b) with
| (true,false) -> ...
| _ -> action c

Si no está familiarizado con la sintaxis, cada patrón está representado por un | seguido de los valores para que coincidan con (a, b), y el guión bajo se usa como comodín para significar "cualquier otro valor". Dado que el único caso en el que queremos hacer algo diferente a la acción c es cuando a es verdadero yb es falso, declaramos explícitamente esos valores como el primer patrón (verdadero, falso) y luego hacemos lo que se debe hacer en ese caso. En todos los demás casos, seguimos el patrón de "comodines" y hacemos acciones c.

Aaron M. Eshbach
fuente
10

La declaración del problema:

Si la condición A coincide, la condición B debe coincidir para realizar la acción C

describe la implicación : A implica B , una proposición lógica equivalente a !A || B(como se menciona en otras respuestas):

bool implies(bool p, bool q) { return !p || q; }

if (implies(/* condition A */,
            /* condition B */))
{
    /* do action C */
}
jamesdlin
fuente
¿Quizás marcarlo inlinepara C y constexprtambién para C ++?
einpoklum
@einpoklum No ingresé algunos de esos detalles porque esta pregunta realmente no especificaba un lenguaje (pero dio un ejemplo con una sintaxis similar a C), así que di una respuesta con una sintaxis similar a C. Personalmente, usaría una macro para que la condición B no se evalúe innecesariamente.
jamesdlin
6

Ugh, esto también me hizo tropezar, pero como señaló Code-Apprentice , estamos garantizados que necesitamos do action Co ejecutamos el elsebloque anidado , por lo tanto, el código podría simplificarse para:

if (not condition A or condition B) {
    do action C
} else {
    ...
}

Así es como estamos llegando a los 3 casos:

  1. El anidado do action Cen la lógica de su pregunta necesaria condition Ay condition Bpara ser true- En esta lógica, si llegamos al 2 nd plazo en el if-statement entonces sabemos que condition Aes truepor lo tanto todo lo que necesitamos para evaluar es que condition Bestrue
  2. El elsebloque anidado en la lógica de su pregunta requería condition Aser truey condition Bser false. La única forma en que podemos alcanzar el elsebloque en esta lógica sería si condition Afuera truey condition Bfuerafalse
  3. El elsebloque externo en la lógica de su pregunta debe condition Aser false: en esta lógica, si condition Aes falso, tambiéndo action C

Apoyos para Code-Apprentice por enderezarme aquí. Sugeriría aceptar su respuesta , ya que la presentó correctamente sin editar: /

Jonathan Mee
fuente
2
Tenga en cuenta que la "condición A" no necesita ser evaluada nuevamente. En C ++, tenemos la ley del medio excluido. Si "no condición A" es falsa, entonces "condición A" es necesariamente verdadera.
Code-Apprentice
1
Debido a la evaluación de cortocircuito, Bse evaluará solo si !Aes falso. Por lo tanto, ambos deben fallar para ejecutar las elsedeclaraciones.
Code-Apprentice
Incluso sin evaluación de cortocircuito !A || Bes falso exactamente cuando ambos !Ay Bson falsos. Por lo tanto, Aserá cierto cuando se elseejecute. No es necesario reevaluar A.
Code-Apprentice
@ Code-Apprentice Bueno, mal olor, excelente observación, he corregido mi respuesta, pero sugerí que aceptaran la suya. Solo estoy tratando de explicarte lo que ya has presentado.
Jonathan Mee
Desearía poder darle otro voto positivo para explicar cada caso.
Code-Apprentice
6

En el concepto lógico, puede resolver este problema de la siguiente manera:

f = ab +! a
f =?

Como un problema comprobado, esto se traduce en f = !a + b. Hay algunas formas de probar el problema, como la tabla de verdad, el mapa de Karnaugh , etc.

Entonces, en los lenguajes basados ​​en C, puede usar lo siguiente:

if(!a || b)
{
   // Do action C
}

PD: Karnaugh Map también se usa para series de condiciones más complicadas. Es un método para simplificar las expresiones de álgebra booleana.

Siyavash Hamdi
fuente
6

Aunque ya hay buenas respuestas, pensé que este enfoque podría ser aún más intuitivo para alguien que es nuevo en el álgebra booleana que evaluar una tabla de verdad.

Lo primero que quiere hacer es mirar, bajo qué condiciones desea ejecutar C. Este es el caso cuando (a & b). También cuando !a. Entonces tienes (a & b) | !a.

Si quieres minimizar puedes continuar. Al igual que en la aritmética "normal", puede multiplicarse.

(a & b) | !a = (a | !a) & (b | !a). a | ! a siempre es cierto, por lo que puedes tacharlo, lo que te deja con el resultado minimizado:b | !a . En caso de que el orden marque la diferencia, porque desea verificar b solo si! A es verdadero (por ejemplo, cuando! A es una comprobación de puntero nulo yb es una operación en el puntero como @LordFarquaad señaló en su comentario), puede Quiero cambiar los dos.

El otro caso (/ * ... * /) siempre se ejecutará cuando c no se ejecute, por lo que podemos ponerlo en el caso else.

También vale la pena mencionar que probablemente tenga sentido de cualquier manera poner la acción c en un método.

Lo que nos deja con el siguiente código:

if (!A || B)
{
    doActionC()  // execute method which does action C
}
else
{
   /* ... */ // what ever happens here, you might want to put it into a method, too.
}

De esta manera, también puede minimizar los términos con más operandos, lo que rápidamente se vuelve feo con las tablas de verdad. Otro buen enfoque son los mapas de Karnaugh. Pero no voy a profundizar en esto ahora.

deetz
fuente
4

Para que el código se parezca más al texto, use banderas booleanas. Si la lógica es especialmente oscura, agregue comentarios.

bool do_action_C;

// Determine whether we need to do action C or just do the "..." action
// If condition A is matched, condition B needs to be matched in order to do action C
if (/* condition A */)
{
    if(/* condition B */)
      do_action_C = true; // have to do action C because blah
    else
      do_action_C = false; // no need to do action C because blarg
}
else
{
  do_action_C = true; // A is false, so obviously have to do action C
}

if (do_action_C)
  {
     DoActionC(); // call the function
  }
else
  {
  ...
  }
anatolyg
fuente
3
if((A && B ) || !A)
{
  //do C
}
else if(!B)
{
  //...
}
Ali
fuente
2

Extraería C a un método y luego saldría de la función lo antes posible en todos los casos. elseLas cláusulas con una sola cosa al final casi siempre deben invertirse si es posible. Aquí hay un ejemplo paso a paso:

Extracto C:

if (A) {
   if (B)
      C();
   else
      D();
} else
   C();

Invierta primero ifpara deshacerse de primero else:

if (!A) {
   C();
   return;
}

if (B)
   C();
else
   D();

Deshazte de la segunda else:

if (!A) {
   C();
   return;
}

if (B) {
   C();
   return;
} 

D();

Y luego puede notar que los dos casos tienen el mismo cuerpo y se pueden combinar:

if (!A || B) {
   C();
   return;
}

D();

Las cosas opcionales para mejorar serían:

  • depende del contexto, pero si !A || Bes confuso, extráigalo a una o más variables para explicar la intención

  • cualquiera de C()o D()es el caso excepcional no debe ir el pasado, así que si D()es la excepción, luego invierta la ifúltima vez

Dave Cousineau
fuente
2

Usar banderas también puede resolver este problema

int flag = 1; 
if ( condition A ) {
    flag = 2;
    if( condition B ) {
        flag = 3;
    }
}
if(flag != 2) { 
    do action C 
}
Spr k
fuente