Как улучшить логику, чтобы проверить, соответствуют ли четыре логических значения некоторым случаям
У меня четыре значения bool
:
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
Допустимые значения:
Scenario 1 | Scenario 2 | Scenario 3
bValue1: true | true | true
bValue2: true | true | false
bValue3: true | true | false
bValue4: true | false | false
Так, например, этот сценарий неприемлем:
bValue1: false
bValue2: true
bValue3: true
bValue4: true
На данный момент я придумал это утверждение if
чтобы обнаружить плохие сценарии:
if(((bValue4 && (!bValue3 || !bValue2 || !bValue1)) ||
((bValue3 && (!bValue2 || !bValue1)) ||
(bValue2 && !bValue1) ||
(!bValue1 && !bValue2 && !bValue3 && !bValue4))
{
// There is some error
}
Можно ли улучшить/упростить эту логику операторов?
Ответы
Ответ 1
Я бы стремился к удобочитаемости: у вас есть только 3 сценария, разберитесь с ними с 3 отдельными ifs:
bool valid = false;
if (bValue1 && bValue2 && bValue3 && bValue4)
valid = true; //scenario 1
else if (bValue1 && bValue2 && bValue3 && !bValue4)
valid = true; //scenario 2
else if (bValue1 && !bValue2 && !bValue3 && !bValue4)
valid = true; //scenario 3
Легко читать и отлаживать, ИМХО. Кроме того, вы можете назначить переменную whichScenario
при выполнении whichScenario
if
.
Только с 3 сценариями я бы не пошел с чем-то таким "если первые 3 значения верны, я могу избежать проверки четвертого значения": это усложнит чтение и поддержку вашего кода.
Не элегантное решение может быть конечно, но в этом случае все в порядке: легко и читабельно.
Если ваша логика усложняется, отбросьте этот код и подумайте об использовании чего-то большего для хранения различных доступных сценариев (как предлагает Zladeck).
Я действительно люблю первое предложение, данное в этом ответе: легко читаемое, не подверженное ошибкам, поддерживаемое
(Почти) не по теме:
Я не пишу много ответов здесь, в StackOverflow. Это действительно забавно, что принятый выше ответ является безусловно самым ценным ответом в моей истории (никогда не имел больше 5-10 голосов, прежде чем я думаю), хотя на самом деле это не то, что я обычно считаю "правильным" способом сделать это.
Но простота часто является "правильным способом сделать это", многие люди, кажется, думают об этом, и я должен думать об этом больше, чем я :)
Ответ 2
Я бы стремился к простоте и удобочитаемости.
bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
bool scenario2 = bValue1 && bValue2 && bValue3 && !bValue4;
bool scenario3 = bValue1 && !bValue2 && !bValue3 && !bValue4;
if (scenario1 || scenario2 || scenario3) {
// Do whatever.
}
Обязательно замените имена сценариев, а также имена флагов чем-то описательным. Если это имеет смысл для вашей конкретной проблемы, вы можете рассмотреть эту альтернативу:
bool scenario1or2 = bValue1 && bValue2 && bValue3;
bool scenario3 = bValue1 && !bValue2 && !bValue3 && !bValue4;
if (scenario1or2 || scenario3) {
// Do whatever.
}
Здесь важна не логика предикатов. Он описывает ваш домен и четко выражает ваши намерения. Ключевым моментом здесь является дать всем входным и промежуточным переменным хорошие имена. Если вы не можете найти правильные имена переменных, это может быть признаком того, что вы неправильно описываете проблему.
Ответ 3
Мы можем использовать карту Карно и сводить ваши сценарии к логическому уравнению. Я использовал карточный решатель Online Karnaugh с цепью для 4 переменных.
Это дает:
Меняя A, B, C, D
на bValue1, bValue2, bValue3, bValue4
, это не что иное, как:
bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4
Итак, ваш оператор if
:
if(!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
// There is some error
}
- Карты Карно особенно полезны, когда у вас много переменных и много условий, которые должны оцениваться как
true
. - После сокращения
true
сценариев до логического уравнения добавление соответствующих комментариев, указывающих true
сценарии, является хорошей практикой.
Ответ 4
Реальный вопрос здесь: что происходит, когда другой разработчик (или даже автор) должен изменить этот код несколько месяцев спустя.
Я бы предложил моделировать это как битовые флаги:
const int SCENARIO_1 = 0x0F; // 0b1111 if using c++14
const int SCENARIO_2 = 0x0E; // 0b1110
const int SCENARIO_3 = 0x08; // 0b1000
bool bValue1 = true;
bool bValue2 = false;
bool bValue3 = false;
bool bValue4 = false;
// boolean -> int conversion is covered by standard and produces 0/1
int scenario = bValue1 << 3 | bValue2 << 2 | bValue3 << 1 | bValue4;
bool match = scenario == SCENARIO_1 || scenario == SCENARIO_2 || scenario == SCENARIO_3;
std::cout << (match ? "ok" : "error");
Если есть еще много сценариев или больше флагов, подход к таблице более читабельен и расширяем, чем использование флагов. Для поддержки нового сценария требуется только одна строка в таблице.
int scenarios[3][4] = {
{true, true, true, true},
{true, true, true, false},
{true, false, false, false},
};
int main()
{
bool bValue1 = true;
bool bValue2 = false;
bool bValue3 = true;
bool bValue4 = true;
bool match = false;
// depending on compiler, prefer std::size()/_countof instead of magic value of 4
for (int i = 0; i < 4 && !match; ++i) {
auto current = scenarios[i];
match = bValue1 == current[0] &&
bValue2 == current[1] &&
bValue3 == current[2] &&
bValue4 == current[3];
}
std::cout << (match ? "ok" : "error");
}
Ответ 5
Мой предыдущий ответ уже принят, я добавляю что-то, что, на мой взгляд, читабельно, легко и в этом случае открыто для будущих изменений:
Начиная с ответа @ZdeslavVojkovic (который я считаю довольно хорошим), я придумал следующее:
#include <iostream>
#include <set>
//using namespace std;
int GetScenarioInt(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
return bValue1 << 3 | bValue2 << 2 | bValue3 << 1 | bValue4;
}
bool IsValidScenario(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
std::set<int> validScenarios;
validScenarios.insert(GetScenarioInt(true, true, true, true));
validScenarios.insert(GetScenarioInt(true, true, true, false));
validScenarios.insert(GetScenarioInt(true, false, false, false));
int currentScenario = GetScenarioInt(bValue1, bValue2, bValue3, bValue4);
return validScenarios.find(currentScenario) != validScenarios.end();
}
int main()
{
std::cout << IsValidScenario(true, true, true, false) << "\n"; // expected = true;
std::cout << IsValidScenario(true, true, false, false) << "\n"; // expected = false;
return 0;
}
Смотрите это на работе здесь
Хорошо, что "элегантное и обслуживаемое" (IMHO) решение, к которому я обычно стремлюсь, но на самом деле для случая OP мой предыдущий ответ "куча вопросов" лучше соответствует требованиям OP, даже если оно не элегантное и не обслуживаемое.
Ответ 6
Я также хотел бы представить другой подход.
Моя идея состоит в том, чтобы преобразовать bools в целое число, а затем сравнить с помощью вариативных шаблонов:
unsigned bitmap_from_bools(bool b) {
return b;
}
template<typename... args>
unsigned bitmap_from_bools(bool b, args... pack) {
return (bitmap_from_bools(b) << sizeof...(pack)) | bitmap_from_bools(pack...);
}
int main() {
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
unsigned summary = bitmap_from_bools(bValue1, bValue2, bValue3, bValue4);
if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u) {
//bad scenario
}
}
Обратите внимание, как эта система может поддерживать до 32 переменных в качестве входных данных. замена unsigned
с unsigned long long
(или uint64_t
) увеличивает поддержку до 64 случаев. Если вам не нравится if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u)
, вы также можете использовать еще один метод вариационных шаблонов:
bool equals_any(unsigned target, unsigned compare) {
return target == compare;
}
template<typename... args>
bool equals_any(unsigned target, unsigned compare, args... compare_pack) {
return equals_any(target, compare) ? true : equals_any(target, compare_pack...);
}
int main() {
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
unsigned summary = bitmap_from_bools(bValue1, bValue2, bValue3, bValue4);
if (!equals_any(summary, 0b1111u, 0b1110u, 0b1000u)) {
//bad scenario
}
}
Ответ 7
Вот упрощенная версия:
if (bValue1&&(bValue2==bValue3)&&(bValue2||!bValue4)) {
// acceptable
} else {
// not acceptable
}
Заметьте, конечно, это решение более запутано, чем оригинальное, его значение может быть сложнее понять.
Обновление: MSalters в комментариях нашел еще более простое выражение:
if (bValue1&&(bValue2==bValue3)&&(bValue2>=bValue4)) ...
Ответ 8
Я не вижу никаких ответов, говорящих о названии сценариев, хотя решение OP делает именно это.
Для меня лучше всего инкапсулировать комментарий каждого сценария к имени переменной или имени функции. Вы скорее проигнорируете комментарий, чем имя, и если ваша логика изменится в будущем, вы с большей вероятностью измените имя, чем комментарий. Вы не можете реорганизовать комментарий.
Если вы планируете повторно использовать эти сценарии за пределами вашей функции (или, возможно, захотите), а затем сделать функцию, которая говорит, что он оценивает ( constexpr
/ noexcept
не обязательно, но рекомендуется):
constexpr bool IsScenario1(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && b2 && b3 && b4; }
constexpr bool IsScenario2(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && b2 && b3 && !b4; }
constexpr bool IsScenario3(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && !b2 && !b3 && !b4; }
Сделайте эти методы класса, если это возможно (например, в решении OP). Вы можете использовать переменные внутри вашей функции, если не думаете, что будете использовать логику повторно:
const auto is_scenario_1 = bValue1 && bValue2 && bValue3 && bValue4;
const auto is_scenario_2 = bvalue1 && bvalue2 && bValue3 && !bValue4;
const auto is_scenario_3 = bValue1 && !bValue2 && !bValue3 && !bValue4;
Компилятор, скорее всего, выяснит, что если bValue1 является ложным, все сценарии являются ложными. Не беспокойтесь о том, чтобы сделать его быстрым, просто правильным и читаемым. Если вы просматриваете свой код и считаете это узким местом, потому что компилятор генерирует субоптимальный код в -O2 или выше, попробуйте переписать его.
Ответ 9
Рассмотрите возможность перевода ваших таблиц как можно напрямую в вашу программу. Управляйте программой со стола, а не подражайте ей с помощью логики.
template<class T0>
auto is_any_of( T0 const& t0, std::initializer_list<T0> il ) {
for (auto&& x:il)
if (x==t0) return true;
return false;
}
сейчас
if (is_any_of(
std::make_tuple(bValue1, bValue2, bValue3, bValue4),
{
{true, true, true, true},
{true, true, true, false},
{true, false, false, false}
}
))
это напрямую, насколько возможно, кодирует вашу таблицу истинности в компилятор.
Живой пример.
Вы также можете использовать std::any_of
напрямую:
using entry = std::array<bool, 4>;
constexpr entry acceptable[] =
{
{true, true, true, true},
{true, true, true, false},
{true, false, false, false}
};
if (std::any_of( begin(acceptable), end(acceptable), [&](auto&&x){
return entry{bValue1, bValue2, bValue3, bValue4} == x;
}) {
}
компилятор может встроить код, исключить любую итерацию и построить свою собственную логику для вас. Между тем, ваш код точно отражает то, как вы решили проблему.
Ответ 10
Я только предоставляю свой ответ здесь, как в комментариях, которые кто-то предложил показать мое решение. Я хочу поблагодарить всех за их понимание.
В итоге я решил добавить три новых "сценария" boolean
метода:
bool CChristianLifeMinistryValidationDlg::IsFirstWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
return (INCLUDE_ITEM1(pEntry) &&
!INCLUDE_ITEM2(pEntry) &&
!INCLUDE_ITEM3(pEntry) &&
!INCLUDE_ITEM4(pEntry));
}
bool CChristianLifeMinistryValidationDlg::IsSecondWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
return (INCLUDE_ITEM1(pEntry) &&
INCLUDE_ITEM2(pEntry) &&
INCLUDE_ITEM3(pEntry) &&
INCLUDE_ITEM4(pEntry));
}
bool CChristianLifeMinistryValidationDlg::IsOtherWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
return (INCLUDE_ITEM1(pEntry) &&
INCLUDE_ITEM2(pEntry) &&
INCLUDE_ITEM3(pEntry) &&
!INCLUDE_ITEM4(pEntry));
}
Затем я смог применить эту свою мою процедуру проверки следующим образом:
if (!IsFirstWeekStudentItems(pEntry) && !IsSecondWeekStudentItems(pEntry) && !IsOtherWeekStudentItems(pEntry))
{
; Error
}
В моем реальном приложении 4 значения bool фактически извлекаются из DWORD
который имеет 4 значения, закодированные в нем.
Еще раз спасибо.
Ответ 11
AC/C++
bool scenario[3][4] = {{true, true, true, true},
{true, true, true, false},
{true, false, false, false}};
bool CheckScenario(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
bool temp[] = {bValue1, bValue2, bValue3, bValue4};
for(int i = 0 ; i < sizeof(scenario) / sizeof(scenario[0]); i++)
{
if(memcmp(temp, scenario[i], sizeof(temp)) == 0)
return true;
}
return false;
}
Этот подход является масштабируемым, как если бы число действительных условий возрастало, вы легко просто добавляете их в список сценариев.
Ответ 12
Легко заметить, что первые два сценария похожи - они разделяют большинство условий. Если вы хотите выбрать, в каком сценарии вы сейчас находитесь, вы можете написать его так (это измененное решение @gian-paolo):
bool valid = false;
if(bValue1 && bValue2 && bValue3)
{
if (bValue4)
valid = true; //scenario 1
else if (!bValue4)
valid = true; //scenario 2
}
else if (bValue1 && !bValue2 && !bValue3 && !bValue4)
valid = true; //scenario 3
Идем дальше, вы можете заметить, что первое логическое значение должно быть всегда истинным, что является условием входа, поэтому вы можете:
bool valid = false;
if(bValue1)
{
if(bValue2 && bValue3)
{
if (bValue4)
valid = true; //scenario 1
else if (!bValue4)
valid = true; //scenario 2
}
else if (!bValue2 && !bValue3 && !bValue4)
valid = true; //scenario 3
}
Более того, теперь вы можете ясно видеть, что bValue2 и bValue3 несколько связаны друг с другом - вы можете извлечь их состояние для некоторых внешних функций или переменных с более подходящим именем (это не всегда легко или целесообразно):
bool valid = false;
if(bValue1)
{
bool bValue1and2 = bValue1 && bValue2;
bool notBValue1and2 = !bValue2 && !bValue3;
if(bValue1and2)
{
if (bValue4)
valid = true; //scenario 1
else if (!bValue4)
valid = true; //scenario 2
}
else if (notBValue1and2 && !bValue4)
valid = true; //scenario 3
}
У этого есть некоторые преимущества и недостатки:
- условия меньше, поэтому легче рассуждать о них,
- легче сделать приятное переименование, чтобы сделать эти условия более понятными,
- но они требуют понимания сферы действия,
- более того, он более жесткий
Если вы прогнозируете, что будут изменения в вышеприведенной логике, вы должны использовать более простой подход, представленный @gian-paolo.
В противном случае, если эти условия будут хорошо установлены и являются своего рода "твердыми правилами", которые никогда не изменятся, рассмотрим мой последний фрагмент кода.
Ответ 13
Каждый ответ слишком сложный и трудный для чтения. Лучшим решением для этого является оператор switch()
. Он читается и упрощает добавление/изменение дополнительных случаев. Компиляторы хорошо подходят для оптимизации операторов switch()
.
switch( (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1) )
{
case 0b1111:
// scenario 1
break;
case 0b0111:
// scenario 2
break;
case 0b0001:
// scenario 3
break;
default:
// fault condition
break;
}
Разумеется, вы можете использовать константы и OR вместе в операторах case
для большей удобочитаемости.
Ответ 14
Небольшое отклонение от тонкого ответа @GianPaolo, которое некоторым может показаться легче читать:
bool any_of_three_scenarios(bool v1, bool v2, bool v3, bool v4)
{
return (v1 && v2 && v3 && v4) // scenario 1
|| (v1 && v2 && v3 && !v4) // scenario 2
|| (v1 && !v2 && !v3 && !v4); // scenario 3
}
if (any_of_three_scenarios(bValue1,bValue2,bValue3,bValue4))
{
// ...
}
Ответ 15
Как было предложено mch, вы можете сделать:
if(!((bValue1 && bValue2 && bValue3) ||
(bValue1 && !bValue2 && !bValue3 && !bValue4))
)
где первая строка охватывает два первых хороших случая, а вторая - последняя.
Live Demo, где я играл, и он проходит ваши дела.
Ответ 16
Я бы также использовал быстрые переменные для ясности. Как отмечалось ранее, сценарий 1 равен сценарию 2, поскольку значение bValue4 не влияет на правду этих двух сценариев.
bool MAJORLY_TRUE=bValue1 && bValue2 && bValue3
bool MAJORLY_FALSE=!(bValue2 || bValue3 || bValue4)
то ваше выражение beomes:
if (MAJORLY_TRUE || (bValue1 && MAJORLY_FALSE))
{
// do something
}
else
{
// There is some error
}
Предоставление значимых имен переменным MAJORTRUE и MAJORFALSE (а также фактически vValue * vars) очень помогло бы с удобочитаемостью и обслуживанием.
Ответ 17
Сосредоточьтесь на читаемости проблемы, а не на конкретном выражении "если".
Хотя это приведет к появлению большего количества строк кода, а некоторые могут считать это излишним или ненужным. Я бы предположил, что абстрагирование ваших сценариев от конкретных логических элементов является лучшим способом обеспечения удобочитаемости.
Разделяя вещи на классы (не стесняйтесь просто использовать функции или какой-либо другой инструмент, который вы предпочитаете) с понятными именами - мы можем гораздо легче показать значения каждого сценария. Что еще более важно, в системе со многими движущимися частями - ее легче поддерживать и присоединяться к вашим существующим системам (опять же, несмотря на то, что дополнительный код включен).
#include <iostream>
#include <vector>
using namespace std;
// These values would likely not come from a single struct in real life
// Instead, they may be references to other booleans in other systems
struct Values
{
bool bValue1; // These would be given better names in reality
bool bValue2; // e.g. bDidTheCarCatchFire
bool bValue3; // and bDidTheWindshieldFallOff
bool bValue4;
};
class Scenario
{
public:
Scenario(Values& values)
: mValues(values) {}
virtual operator bool() = 0;
protected:
Values& mValues;
};
// Names as examples of things that describe your "scenarios" more effectively
class Scenario1_TheCarWasNotDamagedAtAll : public Scenario
{
public:
Scenario1_TheCarWasNotDamagedAtAll(Values& values) : Scenario(values) {}
virtual operator bool()
{
return mValues.bValue1
&& mValues.bValue2
&& mValues.bValue3
&& mValues.bValue4;
}
};
class Scenario2_TheCarBreaksDownButDidntGoOnFire : public Scenario
{
public:
Scenario2_TheCarBreaksDownButDidntGoOnFire(Values& values) : Scenario(values) {}
virtual operator bool()
{
return mValues.bValue1
&& mValues.bValue2
&& mValues.bValue3
&& !mValues.bValue4;
}
};
class Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere : public Scenario
{
public:
Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere(Values& values) : Scenario(values) {}
virtual operator bool()
{
return mValues.bValue1
&& !mValues.bValue2
&& !mValues.bValue3
&& !mValues.bValue4;
}
};
Scenario* findMatchingScenario(std::vector<Scenario*>& scenarios)
{
for(std::vector<Scenario*>::iterator it = scenarios.begin(); it != scenarios.end(); it++)
{
if (**it)
{
return *it;
}
}
return NULL;
}
int main() {
Values values = {true, true, true, true};
std::vector<Scenario*> scenarios = {
new Scenario1_TheCarWasNotDamagedAtAll(values),
new Scenario2_TheCarBreaksDownButDidntGoOnFire(values),
new Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere(values)
};
Scenario* matchingScenario = findMatchingScenario(scenarios);
if(matchingScenario)
{
std::cout << matchingScenario << " was a match" << std::endl;
}
else
{
std::cout << "No match" << std::endl;
}
// your code goes here
return 0;
}
Ответ 18
Это зависит от того, что они представляют.
Например, если 1 является ключом, а 2 и 3 - два человека, которые должны согласиться (за исключением случаев, когда они согласны с NOT
им нужно третье лицо - 4 - для подтверждения), наиболее читаемым может быть:
1 &&
(
(2 && 3)
||
((!2 && !3) && !4)
)
по популярному запросу:
Key &&
(
(Alice && Bob)
||
((!Alice && !Bob) && !Charlie)
)
Ответ 19
Выполнение побитовой операции выглядит очень чисто и понятно.
int bitwise = (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1);
if (bitwise == 0b1111 || bitwise == 0b0111 || bitwise == 0b0001)
{
//satisfying condition
}
Ответ 20
Для ясности я обозначаю a, b, c, d и A, B, C, D для дополнений
bValue1 = a (!A)
bValue2 = b (!B)
bValue3 = c (!C)
bValue4 = d (!D)
Уравнение
1 = abcd + abcD + aBCD
= a (bcd + bcD + BCD)
= a (bc + BCD)
= a (bcd + D (b ^C))
Используйте любые уравнения, которые вам подходят.
Ответ 21
If (!bValue1 || (bValue2 != bValue3) || (!bValue4 && bValue2))
{
// you have a problem
}
- b1 всегда должно быть истинным
- b2 всегда должен равняться b3
- и b4 не может быть ложным, если b2 (и b3) истинны
просто
Ответ 22
Только личное предпочтение по поводу принятого ответа, но я бы написал:
bool valid = false;
// scenario 1
valid = valid || (bValue1 && bValue2 && bValue3 && bValue4);
// scenario 2
valid = valid || (bValue1 && bValue2 && bValue3 && !bValue4);
// scenario 3
valid = valid || (bValue1 && !bValue2 && !bValue3 && !bValue4);
Ответ 23
Во-первых, если вы можете только изменить проверку сценария, я бы сосредоточился на удобочитаемости и просто обернул проверку функцией, чтобы вы могли просто вызвать if(ScenarioA())
.
Теперь, предполагая, что вам действительно нужно/нужно оптимизировать это, я бы рекомендовал преобразовать тесно связанные булевы в постоянные целые числа и использовать на них битовые операторы
public class Options {
public const bool A = 2; // 0001
public const bool B = 4; // 0010
public const bool C = 16;// 0100
public const bool D = 32;// 1000
//public const bool N = 2^n; (up to n=32)
}
...
public isScenario3(int options) {
int s3 = Options.A | Options.B | Options.C;
// for true if only s3 options are set
return options == s3;
// for true if s3 options are set
// return options & s3 == s3
}
Это позволяет выразить сценарии так же просто, как перечисление того, что является его частью, позволяет использовать оператор switch для перехода в правильное состояние и путать разработчиков-разработчиков, которые раньше этого не видели. (С# RegexOptions использует этот шаблон для установки флагов, я не знаю, есть ли библиотека библиотеки c++)
Ответ 24
Вложенные, if
может быть проще читать для некоторых людей. Вот моя версия
bool check(int bValue1, int bValue2, int bValue3, int bValue4)
{
if (bValue1)
{
if (bValue2)
{
// scenario 1-2
return bValue3;
}
else
{
// scenario 3
return !bValue3 && !bValue4;
}
}
return false;
}
Ответ 25
"Самое простое" решение, которое я придумал, заключается в следующем:
bool valid = bValue1 && (bValue2 == bValue3) && (bValue2 ? true : !bValue4);
В принципе, я уменьшил раздувание таким образом:
-
bValue1
всегда должно быть true
-
bValue2
и bValue3
должны всегда быть одинаковыми -
bValue4
должен быть false
тогда и только тогда, когда bValue2
и bValue3
являются ложными
Переход по частям (с очевидным комментарием):
-
bValue1 &&
гарантирует, что bValue1
всегда true
Если это не так, оценка короткого замыкания перестает оцениваться, потому что false && [..]
всегда false
-
(bValue2 == bValue3) &&
гарантирует, что они одинаковы (оба false
или оба negative
) -
(bValue2? true: !bValue4)
гарантирует, что значение bValue4
не имеет значения, если значение bValue2
true
. В соответствии с указанными bValue4
должен быть false
если bValue2
и bValue3
являются ложными.
Как это работает?
Оператор trenary "возвращает" результат, основанный на условии.
Рассмотрим следующий код:
// using the operator
bool value = condition ? true : false;
// using an if
bool value;
if(condition)
{
value = true;
}
else
{
value = false;
}
Я признаю, что нужно немного привыкнуть.
Самое легкое, что я нашел, чтобы помнить о функционировании, - это притвориться, что condition
задают вопрос.
Чтение bValue2? true: !bValue4
bValue2? true: !bValue4
как вопрос можно рассматривать как:
Является ли bValue2
true
? Если да, то дайте мне true
, иначе дайте мне !bValue4
.
Поскольку это оператор присваивания, вы можете сделать следующее:
string type = Chocolate.hasType() ? Chocolate.getType() : "";
Ответ 26
Я согласен с другими ответами, указывающими, что код должен быть чистым и читаемым. Имея это в виду, вы можете сделать несколько маршрутов для достижения чистого и читаемого кода, который эффективно выполняет задачу. Я хотел бы рассмотреть возможность использования массивов для достижения этой задачи, поскольку она обширна и легко читается. Чтобы доказать свою точку зрения, я являюсь разработчиком C#
с минимальным опытом работы на C++
языке (на самом деле мне пришлось Google, как массивы создаются в C++
), и протестировал этот метод с помощью ideone.com.
Я предпочитаю один массив, называемый scenario
и двумерный массив, содержащий приемлемые сценарии, называемые acceptableScenarios
сценариями.
#include <iostream>
using namespace std;
const int WIDTH = 3;
const int HEIGHT = 4;
bool acceptableScenarios[WIDTH][HEIGHT] = {
{ true, true, true, true },
{ true, true, true, false },
{ true, false, false, false }
};
int main() {
bool scenario[4] = { true, false, false, false };
return 0;
}
Затем вы можете быстро сравнить и расширить цикл:
bool IsScenarioAcceptable(bool scenario[]) {
for (int asi = 0; asi < WIDTH; asi++) {
bool match = true;
for (int si = 0; si < HEIGHT; si++)
match &= scenario[si] == acceptableScenarios[asi][si];
if (match)
return true;
}
return false;
}
int main() {
bool scenario[4] = { true, false, false, false };
bool acceptable = IsScenarioAcceptable(scenario);
if (acceptable)
cout << "The supplied scenario is acceptable.";
else
cout << "The supplied scenario is unacceptable.";
return 0;
}
Это позволяет вам иметь как можно больше приемлемых сценариев; хотя у вас всегда будет только так много комбинаций, которые могут быть приемлемыми, в которых максимальный уровень составляет 100%.
Ответ 27
попробуйте присвоить логическим значениям четырехзначное число (0010 или 0001) и использовать метод str.slice(), чтобы увидеть, является ли переменная тем, что вы хотите
Ответ 28
Мои 2 цента: объявить переменную сумму (целое число) так, чтобы
if(bValue1)
{
sum=sum+1;
}
if(bValue2)
{
sum=sum+2;
}
if(bValue3)
{
sum=sum+4;
}
if(bValue4)
{
sum=sum+8;
}
Проверьте сумму против условий, которые вы хотите, и что они. Таким образом, вы можете легко добавить дополнительные условия в будущем, чтобы их было достаточно просто читать.
Ответ 29
Вам не придется беспокоиться о недопустимых комбинациях логических флагов, если вы избавитесь от логических флагов.
Допустимые значения:
Scenario 1 | Scenario 2 | Scenario 3
bValue1: true | true | true
bValue2: true | true | false
bValue3: true | true | false
bValue4: true | false | false
У вас явно есть три состояния (сценарии). Было бы лучше смоделировать это и вывести логические свойства из этих состояний, а не наоборот.
enum State
{
scenario1,
scenario2,
scenario3,
};
inline bool isValue1(State s)
{
// (Well, this is kind of silly. Do you really need this flag?)
return true;
}
inline bool isValue2(State s)
{
switch (s)
{
case scenario1:
case scenario2:
return true;
case scenario3:
return false;
}
}
inline bool isValue3(State s)
{
// (This is silly too. Do you really need this flag?)
return isValue2(s);
}
inline bool isValue4(State s)
{
switch (s)
{
case scenario1:
return true;
case scenario2:
case scenario3:
return false;
}
}
Это определенно больше кода, чем в ответе Джана Паоло, но в зависимости от вашей ситуации это может быть гораздо более удобным:
- Существует центральный набор функций для изменения, если добавляются дополнительные логические свойства или сценарии.
- Добавление свойств требует добавления только одной функции.
- При добавлении сценария включение предупреждений компилятора о необработанных случаях
enum
в операторах switch
будет перехватывать свойства-получатели, которые не обрабатывают этот сценарий.
- Если вам нужно динамически изменять логические свойства, вам не нужно повторно проверять их комбинации везде. Вместо переключения отдельных логических флагов (что может привести к недопустимым комбинациям флагов), вместо этого у вас будет конечный автомат, который переходит от одного сценария к другому.
Этот подход также имеет побочную выгоду, будучи очень эффективным.
Ответ 30
Несколько правильных ответов было дано на этот вопрос, но я бы сделал другое мнение: если код выглядит слишком сложным, что-то не совсем правильно. Код будет трудно отлаживать и, скорее всего, будет "одноразовым".
В реальной жизни, когда мы находим такую ситуацию:
Scenario 1 | Scenario 2 | Scenario 3
bValue1: true | true | true
bValue2: true | true | false
bValue3: true | true | false
bValue4: true | false | false
Когда четыре состояния связаны такой точной схемой, мы имеем дело с конфигурацией некоторого "сущности" в нашей модели.
Крайняя метафора заключается в том, как мы будем описывать "человеческие существа" в модели, если бы мы не знали о их существовании как унитарных сущностях с компонентами, связанными с определенными степенями свободы: нам пришлось бы описывать независимые состояния "торсов", "руки", "ноги" и "голова", что затрудняет понимание описанной системы. Непосредственным результатом были бы неестественно сложные булевы выражения.
Очевидно, что способ уменьшить сложность - это абстракция, а инструментом выбора в c++ является парадигма объекта.
Поэтому возникает вопрос: почему существует такая картина? Что это и что представляет?
Поскольку мы не знаем ответа, мы можем отказаться от математической абстракции: массив: у нас есть три сценария, каждый из которых теперь является массивом.
0 1 2 3
Scenario 1: T T T T
Scenario 2: T T T F
Scenario 3: T F F F
В этот момент у вас есть начальная конфигурация. как массив. Например, std::array
имеет оператор равенства:
В этот момент ваш синтаксис будет выглядеть следующим образом:
if( myarray == scenario1 ) {
// arrays contents are the same
}
else if ( myarray == scenario2 ) {
// arrays contents are the same
}
else if ( myarray == scenario3 ) {
// arrays contents are the same
}
else {
// not the same
}
Как и ответ Джан Паоло, он короткий, понятный и легко проверяемый/отлаживаемый. В этом случае мы делегировали детали булевых выражений компилятору.