Se a condição A for correspondida, a condição B precisará ser correspondida para executar a ação C

148

Minha pergunta é:

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

É possível escrever apenas o código de ação C uma vez em vez de duas vezes?

Como simplificá-lo?

starf15h
fonte
56
Coloque o código para "ação C" em uma função?
CinCout
26
Isto é triste isso realmente não relacionados com a C ++ questão tem que HNQ: /
YSC
2
Obrigado a todos por me ajudar! No começo, eu só quero ter certeza de que está tudo bem, então usei um if aninhado. É porque essa é a maneira mais simples que adivinhei. Vou tentar fazer mais esforços depois de fazer perguntas na próxima vez. Desejo a todos um bom dia :)
starf15h
13
Essa é uma estratégia muito boa: escreva um código que funcione primeiro e depois se preocupe em torná-lo elegante e eficiente posteriormente.
Code-Apprentice
3
@ Tim eu publiquei como resposta. Em uma nota lateral, é triste ver menos votos lá.
22617 CinCout

Respostas:

400

Seu primeiro passo nesse tipo de problema é sempre criar uma tabela lógica.

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

Depois de fazer a mesa, a solução é clara.

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

Observe que essa lógica, embora mais curta, pode ser difícil para futuros programadores manterem.

QuestionC
fonte
35
Eu realmente gosto que você mostrou a tabela da verdade para ajudar o OP a entender como desenvolver isso sozinho. Você pode dar um passo adiante e explicar como você obtém a expressão booleana da tabela verdade? Para alguém novo em programação e lógica booleana, isso provavelmente não é nada claro.
Code-Apprentice
14
Se a avaliação Btiver efeitos colaterais, a tabela lógica deve levar isso em consideração.
Yakk - Adam Nevraumont
79
@Yakk Minha resposta não aborda efeitos colaterais por duas razões. Primeiro, a solução (coincidentemente) tem o comportamento correto dos efeitos colaterais. Em segundo lugar, e mais importante, A e B com efeitos colaterais seriam códigos ruins e uma discussão sobre esse caso adicional seria uma distração para uma pergunta fundamentalmente sobre lógica booleana.
QuestionC
52
Talvez valha a pena notar, caso o A && !Bcaso seja um no-op: !(A && !B)é equivalente ao !A || Bque significa que você pode fazer if (!A || B) { /* do action C */ }e evitar um bloco vazio.
KRyan
54
Se if (A && !B)é realmente difícil para futuros programadores manter, então não há realmente como ajudá-los.
Raio
65

Você tem duas opções:

  1. Escreva uma função que execute a "ação C".

  2. Reorganize sua lógica para que você não tenha tantas instruções if aninhadas. Pergunte a si mesmo que condições causam a "ação C". Parece-me que acontece quando a "condição B" é verdadeira ou a "condição A" é falsa. Podemos escrever isso como "NÃO A OU B". Traduzindo isso para o código C, obtemos

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

Para aprender mais sobre esse tipo de expressão, sugiro pesquisar "álgebra booleana", "lógica de predicados" e "cálculo de predicados". Estes são tópicos matemáticos profundos. Você não precisa aprender tudo, apenas o básico.

Você também deve aprender sobre "avaliação de curto-circuito". Por esse motivo, a ordem das expressões é importante para duplicar exatamente sua lógica original. Embora B || !Aseja logicamente equivalente, usar isso como condição irá executar a "ação C" quando Bfor verdadeira, independentemente do valor de A.

Code-Apprentice
fonte
15
@Yakk Veja as leis de deorgan.
Code-Apprentice
@ Aprendiz de código Por favor, perdoe meu mau pensamento lógico. Gostaria de perguntar se existe alguma diferença entre (! A || B) e (A &&! B). Parece que ambos estão bem para o meu problema. Quero dizer a sua e a abordagem da QuestionC.
starf15h
6
@ Starf15h Há outra diferença crucial: onde a "ação C" é executada. Essa diferença torna nossas duas soluções exatamente equivalentes. Eu sugiro que você pesquise "Leis de Deorgan", que deve ajudá-lo a entender o que está acontecendo aqui.
Code-Apprentice
5
As duas soluções são exatamente equivalentes, mas pode haver uma diferença prática dependendo do que exatamente ...é . Se não for nada (ou seja, “faça C se essas condições forem atendidas; caso contrário, não faça nada”), essa é claramente a solução superior, pois a elseafirmação pode ser deixada de fora por completo.
Janus Bahs Jacquet
1
Além disso, dependendo dos nomes de A e B, esse arranjo pode ser mais legível ou menos legível para um ser humano do que o arranjo do QuestionC.
Michael - Onde está Clay Shirky
15

Você pode simplificar a declaração assim:

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

Caso contrário, coloque o código para 'C' em uma função separada e chame-o:

DoActionC()
{
    ....
    // code for Action C
}
if (condition A)
{
    if(condition B)
      {
         DoActionC(); // call the function
      }
    else
      ...
}
else
{
   DoActionC(); // call the function
}
CinCout
fonte
7
Ou, mais simplesmenteif (!A || B)
Tas
2
Logicamente, (! (A && B) || A) é equivalente a (B || A!)
Code-Apprentice
@ Code-aprendiz B || !Aterá como resultado a trueapenas se Bé true, sem realmente verificando Adevido a um curto-circuito
CinCout
1
@CinCout Bom argumento. Embora minha afirmação ainda seja verdadeira do ponto de vista da lógica booleana teórica, não levei em conta os aspectos práticos dos operadores booleanos de curto-circuito. Felizmente, minha própria resposta tem a ordem correta.
Code-Apprentice
1
Portanto, de uma perspectiva lógica, a ordem não importa. No entanto, do ponto de vista de manutenção e legibilidade, pode haver uma enorme diferença dependendo do que exatamente Ae Brepresenta.
Code-Apprentice
14

Em um idioma com correspondência de padrões, você pode expressar a solução de uma maneira que reflita mais diretamente a tabela de verdade na resposta da PerguntaC.

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

Se você não estiver familiarizado com a sintaxe, cada padrão é representado por um | seguido pelos valores para combinar com (a, b) e o sublinhado é usado como curinga para significar "quaisquer outros valores". Como o único caso em que queremos fazer algo diferente da ação c é quando a é verdadeiro eb é falso, declaramos explicitamente esses valores como o primeiro padrão (verdadeiro, falso) e, então, fazemos o que deve ser feito nesse caso. Em todos os outros casos, passamos ao padrão "curinga" e executamos a ação c.

Aaron M. Eshbach
fonte
10

A declaração do problema:

Se a condição A for correspondida, a condição B precisará ser correspondida para executar a ação C

descreve a implicação : A implica B , uma proposição lógica equivalente a !A || B(como mencionado em outras respostas):

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

if (implies(/* condition A */,
            /* condition B */))
{
    /* do action C */
}
jamesdlin
fonte
Talvez marque-o inlinepara C e constexprtambém para C ++?
einpoklum
@einpoklum Eu não entrei em alguns desses detalhes porque essa pergunta realmente não especificou um idioma (mas deu um exemplo com sintaxe do tipo C), por isso dei uma resposta com sintaxe do tipo C. Pessoalmente, eu usaria uma macro para que a condição B não seja avaliada desnecessariamente.
Jamesdlin 22/07
6

Ugh, isso também me deixou tropeçado, mas, como indicado pelo Code-Apprentice , garantimos que precisamos do action Cou executamos o elsebloco aninhado , portanto, o código pode ser simplificado para:

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

É assim que chegamos aos 3 casos:

  1. O aninhado do action Cna lógica da sua pergunta é necessário condition Ae condition Bdeve ser true- Nessa lógica, se atingirmos o termo no ifdemonstrativo, sabemos que condition Aé, trueportanto, tudo o que precisamos avaliar é o seguinte condition B:true
  2. O elsebloco aninhado na lógica da sua pergunta precisava condition Aser truee condition Bser false- A única maneira de alcançar o elsebloco nessa lógica seria se condition Afosse truee condition Bfossefalse
  3. O elsebloco externo na lógica da sua pergunta precisa condition Aser false- Nesta lógica, se condition Afor falso, tambémdo action C

Suportes ao Code-Apprentice por me endireitar aqui. Sugiro aceitar sua resposta , pois ele a apresentou corretamente sem editar: /

Jonathan Mee
fonte
2
Observe que a "condição A" não precisa ser avaliada novamente. Em C ++, temos a lei do meio excluído. Se a "condição não A" for falsa, a "condição A" será necessariamente verdadeira.
Code-Apprentice
1
Por causa da avaliação de curto-circuito, Bserá avaliado apenas se !Afor falso. Portanto, ambos devem falhar para executar as elseinstruções.
Code-Apprentice
Mesmo sem avaliação de curto-circuito, !A || Bé falso exatamente quando ambos!A e Bsão falsos. Portanto, Aserá verdade quando o elseexecuta. Não há necessidade de reavaliar A.
Code-Apprentice
@ Aprendiz de código Bem fedor, excelente observação, corrigi minha resposta, mas sugeri que a sua fosse aceita. Só estou tentando explicar o que você já apresentou.
Jonathan Mee
Eu gostaria de poder dar outro voto positivo para explicar cada caso.
Code-Apprentice
6

No conceito de lógica, você pode resolver esse problema da seguinte maneira:

f = ab +! a
f =?

Como um problema comprovado, isso resulta em f = !a + b. Existem algumas maneiras de provar o problema, como tabela de verdade, mapa de Karnaugh e assim por diante.

Portanto, em idiomas baseados em C, você pode usar o seguinte:

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

PS: Karnaugh Map também é usado para séries mais complicadas de condições. É um método de simplificar expressões de álgebra booleana.

Siyavash Hamdi
fonte
6

Embora já existam boas respostas, pensei que essa abordagem poderia ser ainda mais intuitiva para alguém que é novo na álgebra booleana do que para avaliar uma tabela da verdade.

A primeira coisa que você quer fazer é procurar, sob quais condições você deseja executar C. É esse o caso quando (a & b). Também quando!a . Então você tem (a & b) | !a.

Se você quiser minimizar, pode continuar. Assim como na aritmética "normal", você pode multiplicar.

(a & b) | !a = (a | !a) & (b | !a). a | ! a é sempre verdade, então você pode simplesmente riscar, o que deixa o resultado minimizado:b | !a . Caso a ordem faça diferença, porque você deseja marcar b apenas se! A for verdadeiro (por exemplo, quando! A é uma verificação de ponteiro nulo eb é uma operação no ponteiro como @LordFarquaad apontado em seu comentário), você pode quer mudar os dois.

O outro caso (/ * ... * /) é sempre será executado quando c não for executado, portanto, podemos apenas colocá-lo no caso else.

Também vale mencionar que provavelmente faz sentido de qualquer maneira colocar a ação c em um método.

O que nos deixa com o seguinte 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.
}

Dessa forma, você também pode minimizar os termos com mais operandos, o que rapidamente fica feio com as tabelas verdadeiras. Outra boa abordagem são os mapas de Karnaugh. Mas não vou aprofundar isso agora.

deetz
fonte
4

Para tornar o código mais parecido com texto, use sinalizadores booleanos. Se a lógica for especialmente obscura, adicione comentários.

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
fonte
3
if((A && B ) || !A)
{
  //do C
}
else if(!B)
{
  //...
}
Todos
fonte
2

Eu extrairia C para um método e sairia da função o mais rápido possível em todos os casos. elsecláusulas com uma única coisa no final quase sempre devem ser invertidas, se possível. Aqui está um exemplo passo a passo:

Extrato C:

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

Inverta primeiro ifpara se livrar do primeiro else:

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

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

Livre-se do segundo else:

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

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

D();

E então você pode perceber que os dois casos têm o mesmo corpo e podem ser combinados:

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

D();

Coisas opcionais para melhorar seriam:

  • depende do contexto, mas se !A || Bfor confuso, extraia-o para uma ou mais variáveis ​​para explicar a intenção

  • qualquer dos C()ou D()é o caso não excepcional deve ir passado, por isso, se D()é a exceção, então invertido a ifuma última vez

Dave Cousineau
fonte
2

O uso de sinalizadores também pode resolver esse problema

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