Eu tenho quatro bool
valores:
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
Os valores aceitáveis são:
Scenario 1 | Scenario 2 | Scenario 3
bValue1: true | true | true
bValue2: true | true | false
bValue3: true | true | false
bValue4: true | false | false
Portanto, por exemplo, este cenário não é aceitável:
bValue1: false
bValue2: true
bValue3: true
bValue4: true
No momento, criei esta if
declaração para detectar cenários ruins:
if(((bValue4 && (!bValue3 || !bValue2 || !bValue1)) ||
((bValue3 && (!bValue2 || !bValue1)) ||
(bValue2 && !bValue1) ||
(!bValue1 && !bValue2 && !bValue3 && !bValue4))
{
// There is some error
}
Essa lógica de instrução pode ser aprimorada / simplificada?
c++
if-statement
Andrew Truckle
fonte
fonte
if
declaração complexa . Além disso, como são sinalizadores booleanos, é possível modelar cada cenário como uma constante e comparar com ele.if (!((bValue1 && bValue2 && bValue3) || (bValue1 && !bValue2 && !bValue3 && !bValue4)))
bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
Respostas:
Eu apontaria para facilitar a leitura: você tem apenas 3 cenários, lide com eles com 3 ifs separados:
Fácil de ler e depurar, IMHO. Além disso, você pode atribuir uma variável
whichScenario
enquanto continua com oif
.Em apenas três cenários, eu não concordaria com algo como "se os 3 primeiros valores forem verdadeiros, posso evitar verificar o quarto valor": isso tornará seu código mais difícil de ler e manter.
Não é uma solução elegante
talvezCertamente, mas neste caso está ok: fácil e legível.Se sua lógica ficar mais complicada, jogue fora esse código e considere usar algo mais para armazenar diferentes cenários disponíveis (como sugere Zladeck).
Eu realmente amo a primeira sugestão dada nesta resposta : fácil de ler, não propenso a erros, sustentável
(Quase) fora do tópico:
Não escrevo muitas respostas aqui no StackOverflow. É realmente engraçado que a resposta acima aceita seja de longe a resposta mais apreciada da minha história (nunca tive mais do que 5 a 10 votos antes), enquanto na verdade não é o que eu normalmente acho que é a maneira "certa" de fazê-lo.
Mas a simplicidade é frequentemente "o caminho certo para fazê-lo", muitas pessoas parecem pensar isso e eu deveria pensar mais do que eu :)
fonte
valid
e separando-as com||
, em vez de fazer a mutaçãovalid
em blocos de instruções separados. Não posso colocar um exemplo no comentário, mas você pode alinhar verticalmente os||
operadores à esquerda para deixar isso muito claro; as condições individuais já estão entre parênteses tanto quanto precisam (paraif
), para que você não precise adicionar caracteres às expressões além do que já está lá.if($bValue1)
maneira que sempre tem que ser verdade, tecnicamente permitindo algumas pequenas melhorias de desempenho (embora falemos de valores insignificantes aqui).bValue4
Eu buscaria simplicidade e legibilidade.
Substitua os nomes dos cenários e os nomes dos sinalizadores por algo descritivo. Se isso faz sentido para o seu problema específico, você pode considerar esta alternativa:
O importante aqui não é a lógica de predicado. Ele está descrevendo seu domínio e expressando claramente sua intenção. A chave aqui é fornecer bons nomes a todas as entradas e variáveis intermediárias. Se você não conseguir encontrar bons nomes de variáveis, pode ser um sinal de que está descrevendo o problema da maneira errada.
fonte
Podemos usar um mapa de Karnaugh e reduzir seus cenários a uma equação lógica. Eu usei o resolvedor de mapas Online Karnaugh com circuito para 4 variáveis.
Isso produz:
Mudando
A, B, C, D
parabValue1, bValue2, bValue3, bValue4
, isso não passa de:Portanto, sua
if
declaração se torna:true
.true
cenários a uma equação lógica, adicionar comentários relevantes indicando ostrue
cenários é uma boa prática.fonte
//!(ABC + AB'C'D') (By K-Map logic)
. Esse seria um bom momento para o desenvolvedor aprender o K-Maps se ele ainda não os conhece.E
eF
condições e 4 novos cenários? Quanto tempo leva para atualizar estaif
declaração corretamente? Como a revisão de código verifica se está ok ou não? O problema não está no lado técnico, mas no lado "comercial".A
:ABC + AB'C'D' = A(BC + B'C'D')
(isso pode ser levado em consideração,A(B ^ C)'(C + D')
embora eu tenha cuidado em chamar isso de 'simplificação').A verdadeira questão aqui é: o que acontece quando outro desenvolvedor (ou mesmo autor) deve alterar esse código alguns meses depois.
Eu sugeriria modelar isso como sinalizadores de bits:
Se houver muito mais cenários ou mais sinalizadores, uma abordagem de tabela é mais legível e extensível do que usar sinalizadores. O suporte a um novo cenário requer apenas mais uma linha na tabela.
fonte
SCENARIO_2 = true << 3 | true << 2 | true << 1 | false;
2: evitar variáveis SCENARIO_X e, em seguida, armazenar todos os cenários disponíveis em a<std::set<int>
. Adicionar um cenário será apenasmySet.insert( true << 3 | false << 2 | true << 1 | false;
um exagero para apenas três cenários, o OP aceitou a solução rápida, suja e fácil que sugeri na minha resposta.std::find
?).scenario
valor me parecem desnecessariamente sujeitas a erros.Minha resposta anterior já é a resposta aceita, adiciono aqui algo que acho que é legível, fácil e, neste caso, aberto a futuras modificações:
Começando com a resposta @ZdeslavVojkovic (que eu acho muito boa), eu vim com isso:
Veja aqui no trabalho
Bem, essa é a solução "elegante e de manutenção" (IMHO) que eu geralmente pretendo, mas realmente, para o caso do OP, minha resposta anterior "grupo de ifs" se encaixa melhor nos requisitos do OP, mesmo que não seja elegante nem sustentável.
fonte
Eu também gostaria de enviar uma outra abordagem.
Minha idéia é converter os bools em um número inteiro e depois comparar usando modelos variados:
Observe como este sistema pode suportar até 32 bools como entrada. substituir o
unsigned
comunsigned long long
(ouuint64_t
) aumenta o suporte para 64 casos. Se você não gostar doif (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u)
, também poderá usar outro método de modelo variável:fonte
bitmap_from_bools
, oubools_to_bitmap
?bools_to_unsigned
. Bitmap é uma boa palavra-chave; editado.summary!= 0b1111u &&...
.a != b || a != c
é sempre verdadeiro seb != c
Aqui está uma versão simplificada:
Observe, é claro, que esta solução é mais ofuscada que a original, seu significado pode ser mais difícil de entender.
Atualização: os MSalters nos comentários encontraram uma expressão ainda mais simples:
fonte
simple
é realmente um termo vago. Muitas pessoas entendem isso neste contexto como mais simples para o desenvolvedor entender e não para o compilador gerar código; portanto, mais detalhado pode realmente ser mais simples.Considere traduzir suas tabelas o mais diretamente possível em seu programa. Dirija o programa com base fora da mesa, em vez de imitá-lo com lógica.
agora
isso diretamente possível codifica sua tabela verdade no compilador.
Exemplo ao vivo .
Você também pode usar
std::any_of
diretamente:o compilador pode incorporar o código e eliminar qualquer iteração e criar sua própria lógica para você. Enquanto isso, seu código reflete exatamente como você se ocultou do problema.
fonte
Estou apenas fornecendo minha resposta aqui, como nos comentários que alguém sugeriu para mostrar minha solução. Quero agradecer a todos por suas idéias.
No final, optei por adicionar três novos
boolean
métodos de "cenário" :Então, eu pude aplicar aqueles a minha rotina de validação como esta:
Na minha aplicação ao vivo, os 4 valores bool são realmente extraídos de um
DWORD
que possui 4 valores codificados.Mais uma vez obrigado a todos.
fonte
INCLUDE_ITEM1
etc de uma maneira melhor e tudo seja bom. :)Não estou vendo nenhuma resposta dizendo o nome dos cenários, embora a solução do OP faça exatamente isso.
Para mim, é melhor encapsular o comentário do que cada cenário faz em um nome de variável ou nome de função. É mais provável que você ignore um comentário do que um nome e, se sua lógica mudar no futuro, é mais provável que você mude um nome do que um comentário. Você não pode refatorar um comentário.
Se você planeja reutilizar esses cenários fora de sua função (ou desejar), crie uma função que diga o que avalia (
constexpr
/noexcept
opcional, mas recomendado):Faça esses métodos de classe, se possível (como na solução do OP). Você pode usar variáveis dentro de sua função se não achar que reutilizará a lógica:
O compilador provavelmente resolverá que, se bValue1 for falso, todos os cenários serão falsos. Não se preocupe em torná-lo rápido, apenas correto e legível. Se você criar um perfil do seu código e achar que isso é um gargalo, porque o compilador gerou um código abaixo do ideal em -O2 ou superior, tente reescrevê-lo.
fonte
Maneira de AC / C ++
Essa abordagem é escalável, como se o número de condições válidas aumentasse, você adiciona mais facilmente à lista de cenários.
fonte
true
. Um compilador que usa "qualquer coisa que não seja zero é verdadeiro" faz com que esse código falhe. Observe quetrue
deve ser convertido para1
, ele simplesmente não precisa ser armazenado como tal.2 is not equal to true but evaluates to true
, meu código não forçaint 1 = true
e funciona desde que todas as verdadeiras sejam convertidas no mesmo valor int, então, aqui está a minha pergunta: Por que o compilador deve agir aleatoriamente ao converter fiel ao int subjacente, você pode elaborar mais?memcmp
para testar condições booleanas não é a maneira C ++, e duvido que seja uma maneira C estabelecida.É fácil perceber que os dois primeiros cenários são semelhantes - eles compartilham a maioria das condições. Se você quiser selecionar em qual cenário está no momento, escreva-o assim (é uma solução modificada do @ gian-paolo ):
Indo além, você pode notar que o primeiro booleano precisa ser sempre verdadeiro, o que é uma condição de entrada, para que você possa terminar com:
Ainda mais, agora você pode ver claramente que bValue2 e bValue3 estão um pouco conectados - você pode extrair seu estado para algumas funções ou variáveis externas com nome mais apropriado (embora isso nem sempre seja fácil ou apropriado):
Fazer dessa maneira tem algumas vantagens e desvantagens:
Se você prevê que haverá mudanças na lógica acima, use uma abordagem mais direta, como apresentado por @ gian-paolo .
Caso contrário, se essas condições estiverem bem estabelecidas e forem "regras sólidas" que nunca mudarão, considere meu último trecho de código.
fonte
Conforme sugerido por mch, você pode fazer:
onde a primeira linha cobre os dois primeiros casos bons e a segunda linha cobre o último.
Live Demo, onde eu brinquei e passa seus casos.
fonte
Uma ligeira variação na boa resposta de @ GianPaolo, que alguns podem achar mais fácil de ler:
fonte
Toda resposta é excessivamente complexa e difícil de ler. A melhor solução para isso é uma
switch()
declaração. É legível e simplifica a adição / modificação de casos adicionais. Compiladores também são bons em otimizarswitch()
declarações.É claro que você pode usar constantes e OU juntas nas
case
instruções para facilitar a leitura.fonte
Eu também usaria variáveis de atalho para maior clareza. Como observado anteriormente, o cenário 1 é igual ao cenário 2, porque o valor de bValue4 não influencia a verdade desses dois cenários.
então sua expressão aparece:
Dar nomes significativos às variáveis MAJORTRUE e MAJORFALSE (assim como, na verdade, ao bValue * vars) ajudaria muito na legibilidade e manutenção.
fonte
Concentre-se na legibilidade do problema, não na declaração "if" específica.
Embora isso produza mais linhas de código, alguns podem considerar um exagero ou desnecessário. Eu sugeriria que abstrair seus cenários dos booleanos específicos é a melhor maneira de manter a legibilidade.
Ao dividir as coisas em classes (fique à vontade para usar apenas funções ou qualquer outra ferramenta que você preferir) com nomes compreensíveis - podemos muito mais facilmente mostrar os significados por trás de cada cenário. Mais importante, em um sistema com muitas partes móveis - é mais fácil manter e ingressar nos sistemas existentes (novamente, apesar da quantidade de código extra que é invocado).
fonte
Depende do que eles representam.
Por exemplo, se 1 é uma chave e 2 e 3 são duas pessoas que devem concordar (exceto se concordarem
NOT
que precisam de uma terceira pessoa - 4 - para confirmar), o mais legível pode ser:pelo pedido popular:
fonte
bValue
.A operação bit a bit parece muito limpa e compreensível.
fonte
Estou denotando a, b, c, d para maior clareza, e A, B, C, D para complementos
Equação
Use qualquer equação que combina com você.
fonte
simples
fonte
Apenas uma preferência pessoal sobre a resposta aceita, mas eu escreveria:
fonte
Primeiro, supondo que você possa modificar apenas a verificação do cenário, eu me concentraria na legibilidade e apenas agruparia a verificação em uma função para que você possa simplesmente chamar
if(ScenarioA())
.Agora, supondo que você realmente queira / precise otimizar isso, eu recomendaria converter os booleanos firmemente vinculados em números inteiros constantes e usar operadores de bits neles
Isso torna a expressão dos cenários tão fácil quanto listar o que faz parte dela, permite que você use uma instrução switch para ir para a condição correta e confunda outros desenvolvedores que nunca viram isso antes. (C # RegexOptions usa esse padrão para definir sinalizadores, não sei se existe um exemplo de biblioteca c ++)
fonte
if
S aninhados podem ser mais fáceis de ler para algumas pessoas. Aqui está a minha versãofonte
bValue1
bloco, você poderá tratar tudo nele como uma nova página nova em seu processo mental. Aposto que a maneira de abordar o problema pode ser algo muito pessoal ou mesmo cultural.Várias respostas corretas foram dadas a essa pergunta, mas eu adotaria uma visão diferente: se o código parecer muito complicado, algo não está certo . O código será difícil de depurar e provavelmente será "somente para uso único".
Na vida real, quando encontramos uma situação como esta:
Quando quatro estados são conectados por um padrão tão preciso, estamos lidando com a configuração de alguma "entidade" em nosso modelo .
Uma metáfora extrema é como descreveríamos um "ser humano" em um modelo, se não estivéssemos cientes de sua existência como entidades unitárias com componentes conectados em graus específicos de liberdade: teríamos que descrever estados independentes de "torso", "braços", "pernas" e "cabeça", o que tornaria complicado entender o sistema descrito. Um resultado imediato seria expressões booleanas artificialmente complicadas.
Obviamente, a maneira de reduzir a complexidade é a abstração e uma ferramenta de escolha em c ++ é o paradigma do objeto. .
Portanto, a pergunta é: por que existe esse padrão? O que é isso e o que representa?
Como não sabemos a resposta, podemos recorrer a uma abstração matemática: a matriz : temos três cenários, cada um dos quais agora é uma matriz.
Nesse ponto, você tem sua configuração inicial. como uma matriz. Por exemplo,
std::array
tem um operador de igualdade:Nesse momento, sua sintaxe se torna:
Assim como a resposta de Gian Paolo, é curta, clara e facilmente verificável / depurável. Nesse caso, delegamos os detalhes das expressões booleanas no compilador.
fonte
Você não precisará se preocupar com combinações inválidas de sinalizadores booleanos se livrar dos sinalizadores booleanos.
Você tem claramente três estados (cenários). Seria melhor modelar isso e derivar as propriedades booleanas desses estados, e não o contrário.
Definitivamente, este é mais código do que na resposta de Gian Paolo , mas, dependendo da sua situação, isso pode ser muito mais sustentável:
enum
casosswitch
não tratados nas instruções capturará getters de propriedades que não lidam com esse cenário.Essa abordagem também tem o benefício colateral de ser muito eficiente.
fonte
Meus 2 centavos: declarar uma soma variável (inteiro) para que
Verifique a soma de acordo com as condições que você deseja e é isso. Dessa forma, você poderá adicionar facilmente mais condições no futuro, mantendo a leitura bastante simples.
fonte
A resposta aceita é boa quando você tem apenas 3 casos e a lógica de cada um é simples.
Mas se a lógica de cada caso for mais complicada ou se houver muitos outros casos, uma opção muito melhor é usar o padrão de design da cadeia de responsabilidades .
Você cria um
BaseValidator
que contém uma referência aBaseValidator
e um métodovalidate
e um método para chamar a validação no validador referenciado.Em seguida, você cria um número de subclasses que herdam
BaseValidator
, substituindo ovalidate
método pela lógica necessária para cada validador.Em seguida, usá-lo é simples, instancie cada um dos seus validadores e defina cada um deles como a raiz dos outros:
Em essência, cada caso de validação possui sua própria classe, responsável por (a) determinar se a validação corresponde àquela caso, e (b) enviar a validação para outra pessoa na cadeia, se não for.
Observe que eu não estou familiarizado com C ++. Eu tentei combinar a sintaxe de alguns exemplos que encontrei online, mas se isso não funcionar, trate-o mais como pseudocódigo. Eu também tenho um exemplo completo de trabalho em Python abaixo que pode ser usado como base, se preferir.
Novamente, você pode encontrar esse exagero no seu exemplo específico, mas ele cria um código muito mais limpo se você acabar com um conjunto de casos muito mais complicado que precisa ser atendido.
fonte
Uma abordagem simples é encontrar a resposta que você considera aceitável.
Sim = (boolean1 && boolean2 && boolean3 && boolean4) + + ...
Agora, se possível, simplifique a equação usando álgebra booleana.
como neste caso, aceitável1 e 2 combinam-se com
(boolean1 && boolean2 && boolean3)
.Portanto, a resposta final é:
fonte
use o campo de bits :
PS :
É uma grande pena para os CPP. Mas, UB não é minha preocupação, verifique em http://coliru.stacked-crooked.com/a/2b556abfc28574a1 .
fonte
unsigned char*
, embora eu ache que simplesmente usar algo como((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1
provavelmente seria mais eficiente.