วิธีปรับปรุงตรรกะเพื่อตรวจสอบว่าค่าบูลีน 4 ค่าตรงกับบางกรณีหรือไม่


118

ฉันมี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
}

ตรรกะคำสั่งนั้นสามารถปรับปรุง / ทำให้ง่ายขึ้นได้หรือไม่?


8
ฉันจะใช้ตารางแทนifคำสั่งที่ซับซ้อน นอกจากนี้เนื่องจากเป็นแฟล็กบูลีนคุณสามารถจำลองสถานการณ์แต่ละสถานการณ์เป็นค่าคงที่และตรวจสอบได้
Zdeslav Vojkovic

3
if (!((bValue1 && bValue2 && bValue3) || (bValue1 && !bValue2 && !bValue3 && !bValue4)))
mch

14
สถานการณ์จริงคืออะไร? บ่อยครั้งสิ่งต่างๆจะง่ายขึ้นมากหากคุณตั้งชื่อให้ถูกต้องเช่นbool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
idclev 463035818

6
การใช้ชื่อที่มีความหมายคุณสามารถแยกเงื่อนไขที่ซับซ้อนแต่ละเงื่อนไขออกเป็นวิธีการและเรียกวิธีนั้นในเงื่อนไข if มันจะอ่านง่ายและบำรุงรักษาได้มากกว่า เช่นดูตัวอย่างที่ให้ไว้ในลิงค์ refactoring.guru/decompose-conditional
Hardik Modha

คำตอบ:


195

ฉันจะตั้งเป้าหมายให้อ่านง่าย: คุณมีเพียง 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

ง่ายต่อการอ่านและแก้ไขข้อบกพร่อง IMHO นอกจากนี้คุณสามารถกำหนดตัวแปรwhichScenarioขณะดำเนินการกับไฟล์if.

ด้วยสถานการณ์เพียง 3 สถานการณ์ฉันจะไม่ใช้บางอย่างเช่น "ถ้า 3 ค่าแรกเป็นจริงฉันสามารถหลีกเลี่ยงการตรวจสอบค่าที่สี่ได้" ซึ่งจะทำให้โค้ดของคุณอ่านและดูแลรักษาได้ยากขึ้น

ไม่ใช่วิธีการแก้ปัญหาที่สวยงาม อาจจะ แน่นอน แต่ในกรณีนี้ก็โอเค: ง่ายและอ่านได้

หากตรรกะของคุณซับซ้อนขึ้นให้ทิ้งรหัสนั้นและพิจารณาใช้บางอย่างเพิ่มเติมเพื่อจัดเก็บสถานการณ์ต่างๆที่มีอยู่ (ตามที่ Zladeck แนะนำ)

ฉันชอบคำแนะนำแรกที่ให้ในคำตอบนี้มาก : อ่านง่ายไม่ผิดพลาดง่ายบำรุงรักษาได้

(เกือบ) ปิดหัวข้อ:

ฉันไม่ได้เขียนคำตอบมากมายที่ StackOverflow เป็นเรื่องตลกจริงๆที่คำตอบที่ได้รับการยอมรับข้างต้นเป็นคำตอบที่ได้รับการชื่นชมมากที่สุดในประวัติศาสตร์ของฉัน (ไม่เคยมีการโหวตมากกว่า 5-10 ครั้งก่อนที่ฉันจะคิด) ในขณะที่จริงๆแล้วไม่ใช่สิ่งที่ฉันคิดว่าเป็นวิธีที่ "ถูกต้อง" ที่จะทำ

แต่ความเรียบง่ายมักเป็น "วิธีที่ถูกต้อง" หลายคนคิดว่าสิ่งนี้และฉันควรจะคิดมากกว่าที่ทำ :)


1
แน่นอน @hessamhedieh ใช้ได้สำหรับสถานการณ์ที่มีอยู่จำนวนน้อยเท่านั้น อย่างที่บอกถ้าสิ่งต่างๆซับซ้อนขึ้นหาอย่างอื่นดีกว่า
Gian Paolo

4
สิ่งนี้สามารถทำให้ง่ายขึ้นโดยการซ้อนเงื่อนไขทั้งหมดลงในตัวเริ่มต้นสำหรับvalidและแยกเงื่อนไขเหล่านี้ด้วย||แทนที่จะกลายพันธุ์validภายในบล็อกคำสั่งแยกกัน ฉันไม่สามารถใส่ตัวอย่างในความคิดเห็นได้ แต่คุณสามารถจัดแนวตัว||ดำเนินการทางด้านซ้ายเพื่อให้ชัดเจนมาก เงื่อนไขแต่ละรายการมีวงเล็บไว้แล้วเท่าที่จำเป็น (สำหรับif) ดังนั้นคุณจึงไม่จำเป็นต้องเพิ่มอักขระใด ๆ ในนิพจน์นอกเหนือจากที่มีอยู่แล้ว
Leushenko

1
@ Leushenko ฉันคิดว่าการผสมวงเล็บ && และ || เงื่อนไขมีข้อผิดพลาดค่อนข้างง่าย (มีบางคนในคำตอบอื่นบอกว่ามีข้อผิดพลาดในวงเล็บในรหัสใน OP อาจได้รับการแก้ไขแล้ว) การจัดตำแหน่งที่เหมาะสมสามารถช่วยได้อย่างแน่นอน แต่ข้อดีคืออะไร? น่าอ่านมากขึ้น? ดูแลรักษาง่ายกว่า? ฉันไม่คิดอย่างนั้น แค่ความคิดเห็นของฉันแน่นอน ต้องแน่ใจว่าฉันเกลียดการมี ifs ในโค้ดมาก
Gian Paolo

3
ฉันได้สรุปไว้ในสิ่งif($bValue1)ที่ต้องเป็นจริงเสมอในทางเทคนิคช่วยให้สามารถปรับปรุงประสิทธิภาพได้เล็กน้อย (แม้ว่าเรากำลังพูดถึงจำนวนเล็กน้อยที่นี่)
Martijn

2
FWIW: มีเพียง 2 สถานการณ์เท่านั้น 2 สถานการณ์แรกเป็นสถานการณ์เดียวกันและไม่ขึ้นอยู่กับbValue4
Dancrumb

123

ฉันจะมุ่งเป้าไปที่ความเรียบง่ายและอ่านง่าย

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
+1 นี่คือสิ่งที่ฉันจะทำเช่นกัน เช่นเดียวกับที่ @RedFilter ชี้ให้เห็นและตรงกันข้ามกับคำตอบที่ยอมรับนี่เป็นการจัดทำเอกสารด้วยตนเอง การตั้งชื่อสถานการณ์ของตนเองในขั้นตอนแยกต่างหากนั้นอ่านได้ง่ายกว่ามาก
Andreas

106

เราสามารถใช้แผนที่ Karnaughและลดสถานการณ์ของคุณเป็นสมการเชิงตรรกะ ฉันใช้โปรแกรมแก้แผนที่ 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
}
  • Karnaugh trueแผนที่จะมีประโยชน์โดยเฉพาะอย่างยิ่งเมื่อคุณมีหลายตัวแปรและเงื่อนไขหลายอย่างที่ควรประเมิน
  • หลังจากลดtrueสถานการณ์เป็นสมการเชิงตรรกะแล้วการเพิ่มความคิดเห็นที่เกี่ยวข้องซึ่งบ่งชี้ว่าtrueสถานการณ์เป็นแนวทางปฏิบัติที่ดี

96
แม้ว่าจะถูกต้องในทางเทคนิค แต่โค้ดนี้ต้องใช้ความคิดเห็นจำนวนมากเพื่อที่จะได้รับการแก้ไขโดยนักพัฒนารายอื่นในอีกไม่กี่เดือนต่อมา
Zdeslav Vojkovic

22
@ZdeslavVojkovic: ฉันจะเพิ่มความคิดเห็นด้วยสมการ //!(ABC + AB'C'D') (By K-Map logic). นั่นจะเป็นช่วงเวลาที่ดีสำหรับนักพัฒนาซอฟต์แวร์ในการเรียนรู้ K-Maps หากเขายังไม่รู้จักพวกเขา
PW

11
ฉันเห็นด้วยกับสิ่งนั้น แต่ IMO ปัญหาคือมันไม่ได้จับคู่กับโดเมนปัญหาอย่างชัดเจนกล่าวคือเงื่อนไขแต่ละข้อจับคู่กับสถานการณ์เฉพาะซึ่งทำให้ยากต่อการเปลี่ยนแปลง / ขยาย จะเกิดอะไรขึ้นเมื่อมีEและFเงื่อนไขและ 4 สถานการณ์ใหม่ ใช้เวลานานเท่าใดในการปรับปรุงifคำชี้แจงนี้ให้ถูกต้อง? Code review จะตรวจสอบได้อย่างไรว่าใช้ได้หรือไม่? ปัญหาไม่ได้อยู่ที่ด้านเทคนิค แต่เป็นด้าน "ธุรกิจ"
Zdeslav Vojkovic

7
ฉันคิดว่าคุณสามารถแยกตัวประกอบได้A: ABC + AB'C'D' = A(BC + B'C'D')(สิ่งนี้สามารถนำมาพิจารณาได้A(B ^ C)'(C + D')แม้ว่าฉันจะระมัดระวังในการเรียกสิ่งนี้ว่า 'การทำให้เข้าใจง่าย')
Maciej Piechotka

28
@PW ความคิดเห็นนั้นดูเหมือนจะเข้าใจได้พอ ๆ กับรหัสและจึงไม่มีจุดหมายเล็กน้อย ความคิดเห็นที่ดีกว่าจะอธิบายได้ว่าคุณสร้างสมการนั้นขึ้นมาได้อย่างไรกล่าวคือคำสั่งควรทริกเกอร์สำหรับ TTTT, TTTF และ TFFF เมื่อถึงจุดนั้นคุณอาจเขียนเงื่อนไขทั้งสามนี้ลงในโค้ดแทนและไม่ต้องการคำอธิบายเลย
Bernhard Barker

58

คำถามที่แท้จริงคือ: จะเกิดอะไรขึ้นเมื่อนักพัฒนารายอื่น (หรือแม้แต่ผู้เขียน) ต้องเปลี่ยนรหัสนี้ในอีกไม่กี่เดือนต่อมา

ฉันขอแนะนำให้สร้างแบบจำลองนี้เป็นแฟล็กบิต:

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");
}

4
ไม่ใช่วิธีที่บำรุงรักษาได้มากที่สุด แต่ช่วยลดความซับซ้อนของเงื่อนไข if ดังนั้นการแสดงความคิดเห็นเล็กน้อยเกี่ยวกับการดำเนินการแบบบิตจะเป็นสิ่งจำเป็นอย่างยิ่งที่นี่ imo
Adam Zahran

6
IMO ตารางเป็นแนวทางที่ดีที่สุดเนื่องจากปรับขนาดได้ดีขึ้นด้วยสถานการณ์และแฟล็กเพิ่มเติม
Zdeslav Vojkovic

ฉันชอบโซลูชันแรกของคุณอ่านง่ายและเปิดกว้างสำหรับการปรับเปลี่ยน ฉันจะทำการปรับปรุง 2 อย่าง: 1: กำหนดค่าให้กับ scenarioX ด้วยการระบุค่าบูลีนที่ใช้อย่างชัดเจนเช่นSCENARIO_2 = true << 3 | true << 2 | true << 1 | false;2: หลีกเลี่ยงตัวแปร SCENARIO_X แล้วเก็บสถานการณ์ที่มีอยู่ทั้งหมดในไฟล์<std::set<int>. การเพิ่มสถานการณ์จะเป็นเพียงบางสิ่งบางอย่างที่mySet.insert( true << 3 | false << 2 | true << 1 | false;อาจมากเกินไปสำหรับ 3 สถานการณ์ OP ยอมรับวิธีแก้ปัญหาที่รวดเร็วสกปรกและง่ายที่ฉันแนะนำในคำตอบของฉัน
Gian Paolo

4
หากคุณใช้ C ++ 14 ขึ้นไปฉันขอแนะนำให้ใช้ไบนารีลิเทอรัลแทนสำหรับโซลูชันแรก - 0b1111, 0b1110 และ 0b1000 นั้นชัดเจนกว่ามาก คุณอาจทำให้สิ่งนี้ง่ายขึ้นเล็กน้อยโดยใช้ไลบรารีมาตรฐาน ( std::find?)
Bernhard Barker

2
ฉันพบว่าลิเทอรัลไบนารีที่นี่เป็นข้อกำหนดขั้นต่ำในการทำให้โค้ดแรกสะอาด ในรูปแบบปัจจุบันมันเป็นความลับอย่างสมบูรณ์ ตัวระบุที่สื่อความหมายอาจช่วยได้ แต่ฉันไม่แน่ใจด้วยซ้ำ ในความเป็นจริงการดำเนินการบิตเพื่อสร้างscenarioมูลค่าทำให้ฉันเกิดข้อผิดพลาดโดยไม่จำเป็น
Konrad Rudolph

27

คำตอบก่อนหน้าของฉันเป็นคำตอบที่ยอมรับแล้วฉันเพิ่มบางสิ่งที่นี่ซึ่งฉันคิดว่าทั้งอ่านง่ายง่ายและในกรณีนี้จะเปิดให้มีการแก้ไขในอนาคต:

เริ่มต้นด้วยคำตอบ @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 คำตอบ "ifs" ก่อนหน้านี้ของฉันตรงกับข้อกำหนด OP ได้ดีกว่าแม้ว่ามันจะไม่สวยหรูหรือบำรุงรักษาก็ตาม


คุณรู้ว่าคุณสามารถแก้ไขคำตอบก่อนหน้าและปรับปรุงได้ตลอดเวลา
Andreas

20

ฉันต้องการส่งแนวทางอื่นด้วย

ความคิดของฉันคือการแปลงบูลเป็นจำนวนเต็มจากนั้นเปรียบเทียบโดยใช้เทมเพลตตัวแปร:

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
    }
}

2
ฉันชอบแนวทางนี้ยกเว้นชื่อฟังก์ชันหลัก:“ from bool … to what ?” - ทำไมไม่ชัดเจนbitmap_from_boolsหรือbools_to_bitmap?
Konrad Rudolph

ใช่ @KonradRudolph bools_to_unsignedผมไม่ได้คิดชื่อที่ดีกว่าอาจจะยกเว้น บิตแมปเป็นคำหลักที่ดี แก้ไข
Stack Danny

summary!= 0b1111u &&...ฉันคิดว่าคุณต้องการ a != b || a != cเป็นจริงเสมอถ้าb != c
MooseBoys

17

นี่คือเวอร์ชันที่เรียบง่าย:

if (bValue1 && (bValue2 == bValue3) && (bValue2 || !bValue4)) {
    // acceptable
} else {
    // not acceptable
}

โปรดทราบว่าโซลูชันนี้มีความสับสนมากกว่าวิธีเดิมความหมายอาจเข้าใจยากกว่า


อัปเดต: MSalters ในความคิดเห็นพบว่ามีการแสดงออกที่ง่ายกว่า:

if (bValue1&&(bValue2==bValue3)&&(bValue2>=bValue4)) ...

1
ใช่ แต่ยากที่จะเข้าใจ แต่ขอบคุณสำหรับคำแนะนำ
Andrew Truckle

ผมเทียบความสามารถของคอมไพเลอร์เพื่อให้ง่ายต่อการแสดงออกด้วยความเรียบง่ายของคุณเป็นข้อมูลอ้างอิง: คอมไพเลอร์สำรวจ gcc ไม่พบเวอร์ชันที่ดีที่สุดของคุณ แต่วิธีแก้ปัญหายังดีอยู่ ดูเหมือนว่า Clang และ MSVC จะไม่ทำการลดความซับซ้อนของนิพจน์บูลีน
Oliv

1
@AndrewTruckle: โปรดทราบว่าหากคุณต้องการเวอร์ชันที่อ่านได้มากขึ้นโปรดพูดเช่นนั้น คุณได้กล่าวว่า "ง่าย" แต่คุณยอมรับเวอร์ชันที่ละเอียดยิ่งกว่าเวอร์ชันดั้งเดิมของคุณ
geza

1
simpleเป็นคำที่คลุมเครือ หลายคนเข้าใจว่าในบริบทนี้ง่ายกว่าสำหรับนักพัฒนาที่จะเข้าใจและไม่ใช่สำหรับคอมไพเลอร์ในการสร้างโค้ดดังนั้น verbose ที่มากขึ้นจึงอาจง่ายกว่า
Zdeslav Vojkovic

1
@IsmaelMiguel: เมื่อสูตรตรรกะได้รับการปรับให้เหมาะสมกับจำนวนคำความหมายดั้งเดิมมักจะหายไป แต่เราสามารถแสดงความคิดเห็นเพื่อให้ชัดเจนว่ามันทำอะไร สำหรับคำตอบที่ได้รับการยอมรับความคิดเห็นก็ไม่เป็นอันตราย
geza

12

พิจารณาแปลตารางของคุณให้ตรงที่สุดเท่าที่จะทำได้ในโปรแกรมของคุณ ขับเคลื่อนโปรแกรมโดยอิงจากตารางแทนที่จะเลียนแบบด้วยตรรกะ

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;
}) {
}

คอมไพเลอร์สามารถอินไลน์โค้ดและกำจัดการวนซ้ำและสร้างตรรกะของตัวเองสำหรับคุณ ในขณะเดียวกันรหัสของคุณจะแสดงให้เห็นว่าคุณสรุปปัญหาได้อย่างไร


เวอร์ชันแรกอ่านง่ายมากและดูแลรักษาได้ดีฉันชอบมันมาก อันที่สองอ่านยากกว่าอย่างน้อยสำหรับฉันและต้องใช้ระดับทักษะ c ++ ซึ่งอาจจะสูงกว่าค่าเฉลี่ยมากกว่าระดับของฉัน ไม่ใช่สิ่งที่ทุกคนสามารถเขียนได้ เพิ่งเรียนรู้อะไรใหม่ ๆ ขอบคุณ
Gian Paolo

11

ฉันให้คำตอบที่นี่เท่านั้นตามความคิดเห็นที่มีคนแนะนำให้แสดงวิธีแก้ปัญหาของฉัน ฉันอยากจะขอบคุณทุกคนสำหรับข้อมูลเชิงลึกของพวกเขา

ในที่สุดฉันก็เลือกที่จะเพิ่ม "สถานการณ์" ใหม่สาม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 ค่าถูกดึงมาจากค่าDWORDที่มี 4 ค่าที่เข้ารหัสไว้

ขอบคุณทุกคนอีกครั้ง


1
ขอบคุณสำหรับการแบ่งปันวิธีแก้ปัญหา :) มันดีกว่าการซับซ้อนถ้าเงื่อนไขนรก บางทีคุณยังสามารถตั้งชื่อINCLUDE_ITEM1ฯลฯ ในทางที่ดีขึ้นและคุณก็สบายดี :)
Hardik Modha

1
@HardikModha ในทางเทคนิคแล้วพวกเขาเป็น "สิ่งของนักเรียน" และธงมีไว้เพื่อระบุว่าจะ "รวม" หรือไม่ ดังนั้นฉันคิดว่าชื่อแม้ว่าจะฟังดูธรรมดา แต่ก็มีความหมายในบริบทนี้ :)
Andrew Truckle

11

ฉันไม่เห็นคำตอบใด ๆ ที่บอกให้ตั้งชื่อสถานการณ์แม้ว่าโซลูชันของ 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 ขึ้นไปให้ลองเขียนใหม่


ฉันชอบวิธีนี้มากกว่าโซลูชัน (ดีอยู่แล้ว) ของ Gian Paolo เล็กน้อย: หลีกเลี่ยงการควบคุมการไหลและการใช้ตัวแปรที่เขียนทับ - รูปแบบการทำงานที่มากขึ้น
Dirk Herrmann

9

ทาง 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;
}

วิธีนี้สามารถปรับขนาดได้ราวกับว่าจำนวนเงื่อนไขที่ถูกต้องเพิ่มขึ้นคุณเพียงแค่เพิ่มมากขึ้นในรายการสถานการณ์


ฉันค่อนข้างมั่นใจว่านี่ผิด ก็ถือว่าคอมไพเลอร์จะใช้เพียงแทน binary trueเดียวสำหรับ คอมไพเลอร์ที่ใช้ "อะไรก็ตามที่ไม่ใช่ศูนย์เป็นจริง" ทำให้โค้ดนี้ล้มเหลว โปรดทราบว่าtrueต้องแปลงไป1มันก็ไม่จำเป็นที่จะต้องเก็บไว้เป็นเช่นนี้
MSalters

@MSalters, tnx ฉันเข้าใจประเด็นของคุณแล้วและฉันก็รู้ว่า2 is not equal to true but evaluates to trueรหัสของฉันไม่บังคับint 1 = trueและใช้งานได้ตราบเท่าที่ true ทั้งหมดถูกแปลงเป็นค่า int เดียวกันดังนั้นนี่คือคำถามของฉัน: ทำไมคอมไพเลอร์ควรดำเนินการแบบสุ่มในการแปลง จริงกับ int ที่ซ่อนอยู่คุณช่วยอธิบายเพิ่มเติมได้ไหม
hessam hedieh

การดำเนินการmemcmpเพื่อทดสอบเงื่อนไขบูลีนไม่ใช่วิธี C ++ และฉันค่อนข้างสงสัยว่าเป็นวิธี C ที่กำหนดเช่นกัน
Konrad Rudolph

@hessamhedieh: ปัญหาในตรรกะของคุณคือ "การแปลงค่าจริงเป็น int พื้นฐาน" นั่นไม่ใช่วิธีการทำงานของคอมไพเลอร์
MSalters

รหัสของคุณเพิ่มความซับซ้อนจาก O (1) ถึง O (n) ไม่ใช่วิธีที่จะไปในภาษาใด ๆ - เว้น C / C ++ ไว้
mabel

9

สังเกตได้ง่ายว่าสองสถานการณ์แรกมีความคล้ายคลึงกัน - มีการแบ่งปันเงื่อนไขส่วนใหญ่ หากคุณต้องการเลือกสถานการณ์ที่คุณอยู่ในขณะนี้คุณสามารถเขียนได้ดังนี้ (เป็นวิธีแก้ไขของ@ 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

มิฉะนั้นหากเงื่อนไขเหล่านี้ได้รับการกำหนดไว้อย่างดีและเป็น "กฎที่มั่นคง" ที่จะไม่มีวันเปลี่ยนแปลงให้พิจารณาข้อมูลโค้ดสุดท้ายของฉัน


7

ตามที่แนะนำโดย mch คุณสามารถทำได้:

if(!((bValue1 && bValue2 && bValue3) || 
  (bValue1 && !bValue2 && !bValue3 && !bValue4))
)

โดยที่บรรทัดแรกครอบคลุมกรณีที่ดีสองกรณีแรกและบรรทัดที่สองครอบคลุมกรณีสุดท้าย

Live Demoที่ฉันเล่นไปรอบ ๆ และมันผ่านคดีของคุณ


7

การเปลี่ยนแปลงเล็กน้อยในคำตอบที่ดีของ @ 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))
{
  // ...
}

7

ทุกคำตอบซับซ้อนเกินไปและอ่านยาก ทางออกที่ดีที่สุดสำหรับเรื่องนี้คือ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;
}

แน่นอนคุณสามารถใช้ค่าคงที่และหรือหรือค่าเหล่านี้ร่วมกันในcaseข้อความเพื่อให้อ่านง่ายยิ่งขึ้น


ในฐานะที่เป็นโปรแกรมเมอร์ C รุ่นเก่าฉันจะกำหนดมาโคร "PackBools" และใช้ทั้งคู่สำหรับ "สวิตช์ (PackBools (a, b, c, d))" และสำหรับกรณีเช่นโดยตรง "case PackBools (true , จริง ... ) "หรือกำหนดเป็นค่าคงที่ท้องถิ่น" const ไม่ได้ลงนาม int สถานการณ์ 1 = PackBools (จริงจริง ... ); "
Simon F

6

ฉันจะใช้ตัวแปรทางลัดเพื่อความชัดเจน ตามที่ระบุไว้ก่อนหน้าสถานการณ์ที่ 1 เท่ากับสถานการณ์ที่ 2 เนื่องจากค่าของ bValue4 ไม่ส่งผลต่อความจริงของสองสถานการณ์ดังกล่าว

bool MAJORLY_TRUE=bValue1 && bValue2 && bValue3
bool MAJORLY_FALSE=!(bValue2 || bValue3 || bValue4)

จากนั้นการแสดงออกของคุณจะเกิดขึ้น:

if (MAJORLY_TRUE || (bValue1 && MAJORLY_FALSE))
{
     // do something
}
else
{
    // There is some error
}

การตั้งชื่อที่มีความหมายให้กับตัวแปร MAJORTRUE และ MAJORFALSE (เช่นเดียวกับ bValue * vars) จะช่วยได้มากในเรื่องการอ่านและการบำรุงรักษา


6

เน้นที่ความสามารถในการอ่านของปัญหาไม่ใช่คำสั่ง "if" ที่เฉพาะเจาะจง

แม้ว่าสิ่งนี้จะทำให้เกิดบรรทัดของโค้ดมากขึ้นและบางคนอาจคิดว่ามันเกินความจำเป็นหรือไม่จำเป็น ฉันขอแนะนำว่าการทำให้สถานการณ์ของคุณเป็นนามธรรมจากบูลีนเฉพาะเป็นวิธีที่ดีที่สุดในการรักษาการอ่าน

การแบ่งสิ่งต่างๆออกเป็นชั้นเรียน (อย่าลังเลที่จะใช้ฟังก์ชันหรือเครื่องมืออื่น ๆ ที่คุณต้องการ) ด้วยชื่อที่เข้าใจได้เราสามารถแสดงความหมายเบื้องหลังแต่ละสถานการณ์ได้ง่ายขึ้นมาก ที่สำคัญกว่านั้นคือในระบบที่มีชิ้นส่วนเคลื่อนไหวจำนวนมาก - การบำรุงรักษาและเข้าร่วมกับระบบที่มีอยู่ของคุณจะง่ายกว่า (อีกครั้งแม้ว่าจะมีการเข้ารหัสโค้ดพิเศษมากแค่ไหนก็ตาม)

#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;
}

5
ในบางจุดความฟุ่มเฟื่อยเริ่มส่งผลเสียต่อการอ่าน ฉันคิดว่ามันไปไกลเกินไป
JollyJoker

2
@JollyJoker ฉันเห็นด้วยกับสถานการณ์เฉพาะนี้ - อย่างไรก็ตามความรู้สึกในใจของฉันจากวิธีที่ OP ตั้งชื่อทุกอย่างโดยทั่วไปคือรหัส "จริง" ของพวกเขาน่าจะซับซ้อนกว่าตัวอย่างที่พวกเขาให้ไว้มาก จริงๆแล้วฉันแค่อยากจะเสนอทางเลือกนี้ออกไปเพราะมันเป็นวิธีที่ฉันจัดโครงสร้างสำหรับบางสิ่งที่ซับซ้อน / เกี่ยวข้อง แต่คุณพูดถูก - สำหรับตัวอย่างเฉพาะของ OPs มันเป็นเรื่องที่ละเอียดเกินไปและทำให้เรื่องแย่ลง

5

ขึ้นอยู่กับสิ่งที่พวกเขาเป็นตัวแทน

ตัวอย่างเช่นถ้า1คือกุญแจและ2และ3คือบุคคล 2 คนที่ต้องเห็นด้วย (ยกเว้นว่าพวกเขายอมรับNOTว่าต้องการบุคคลที่สาม - 4 - เพื่อยืนยัน) สิ่งที่อ่านได้มากที่สุดอาจเป็น:

1 &&
    (
        (2 && 3)   
        || 
        ((!2 && !3) && !4)
    )

ตามคำขอยอดนิยม:

Key &&
    (
        (Alice && Bob)   
        || 
        ((!Alice && !Bob) && !Charlie)
    )

2
คุณอาจจะพูดถูก แต่การใช้ตัวเลขเพื่อแสดงประเด็นของคุณจะทำให้คุณไม่เข้าใจคำตอบ ลองใช้ชื่อที่สื่อความหมาย
jxh

1
@jxh นี่คือหมายเลข OP ที่ใช้ ฉันเพิ่งลบไฟล์bValue.
ispiro

@jxh ฉันหวังว่าตอนนี้จะดีขึ้น
ispiro

4

การดำเนินการในระดับบิตดูสะอาดและเข้าใจได้มาก

int bitwise = (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1);
if (bitwise == 0b1111 || bitwise == 0b0111 || bitwise == 0b0001)
{
    //satisfying condition
}

1
การเปรียบเทียบระดับบิตดูเหมือนจะอ่านได้สำหรับฉัน ในทางกลับกันองค์ประกอบก็ดูประดิษฐ์
xtofl

3

ฉันหมายถึง 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))

ใช้สมการใด ๆ ที่เหมาะกับคุณ


3
If (!bValue1 || (bValue2 != bValue3) || (!bValue4 && bValue2))
{
// you have a problem
}
  • b1 ต้องเป็นจริงเสมอ
  • b2 ต้องเท่ากับ b3 เสมอ
  • และ b4 ไม่สามารถเป็นเท็จได้หาก b2 (และ b3) เป็นจริง

ง่าย


3

เป็นเพียงความชอบส่วนตัวมากกว่าคำตอบที่ยอมรับ แต่ฉันจะเขียน:

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);

2

ขั้นแรกสมมติว่าคุณสามารถปรับเปลี่ยนการตรวจสอบสถานการณ์ได้เท่านั้นฉันจะเน้นที่ความสามารถในการอ่านและเพียงแค่รวมฟังก์ชันเช็คอินเพื่อให้คุณสามารถโทร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
}

สิ่งนี้ทำให้การแสดงสถานการณ์เป็นเรื่องง่ายเหมือนกับการระบุสิ่งที่เป็นส่วนหนึ่งของมันช่วยให้คุณใช้คำสั่งสวิตช์เพื่อข้ามไปยังเงื่อนไขที่เหมาะสมและสร้างความสับสนให้กับนักพัฒนารายอื่นที่ไม่เคยเห็นมาก่อน (C # RegexOptions ใช้รูปแบบนี้ในการตั้งค่าแฟล็กฉันไม่รู้ว่ามีตัวอย่างไลบรารี c ++ หรือไม่)


ในความเป็นจริงฉันไม่ได้ใช้ค่าบูลสี่ค่า แต่เป็น DWORD ที่มี BOOLS ในตัวสี่ตัว สายเกินไปที่จะเปลี่ยนตอนนี้ แต่ขอบคุณสำหรับข้อเสนอแนะ
Andrew Truckle

2

คำที่ซ้อนกัน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;
}

โดยส่วนตัวแล้วฉันมักจะหลีกเลี่ยงการซ้อนงบถ้าเป็นไปได้ แม้ว่ากรณีนี้จะดีและอ่านง่าย แต่เมื่อมีการเพิ่มความเป็นไปได้ใหม่ ๆ การซ้อนอาจกลายเป็นเรื่องยากที่จะอ่าน แต่ถ้าสถานการณ์ไม่เคยเปลี่ยนแปลงแน่นอนว่าเป็นทางออกที่ดีและอ่านได้
Dnomyar96

@ dnomyar96 ฉันเห็นด้วย ฉันเองก็หลีกเลี่ยง ifs ที่ซ้อนกันเกินไป บางครั้งถ้าตรรกะซับซ้อนฉันจะเข้าใจตรรกะได้ง่ายขึ้นโดยแยกมันออกเป็นชิ้น ๆ ตัวอย่างเช่นเมื่อคุณเข้าสู่bValue1บล็อกแล้วคุณอาจถือว่าทุกอย่างในนั้นเป็นหน้าใหม่ในกระบวนการทางจิตของคุณ ฉันพนันได้เลยว่าวิธีการเข้าถึงปัญหาอาจเป็นเรื่องส่วนตัวหรือแม้แต่วัฒนธรรม
sardok

1

คำถามนี้ได้รับคำตอบที่ถูกต้องหลายข้อ แต่ฉันจะใช้มุมมองที่แตกต่างออกไป: หากโค้ดดูซับซ้อนเกินไปมีบางอย่างไม่ถูกต้องถ้ารหัสดูซับซ้อนเกินไปเป็นสิ่งที่ไม่ถูกต้องโค้ดจะแก้ไขข้อบกพร่องได้ยากและมีแนวโน้มที่จะเป็น "แบบใช้ครั้งเดียวเท่านั้น"

ในชีวิตจริงเมื่อเราพบสถานการณ์เช่นนี้:

         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

}

เช่นเดียวกับคำตอบของ Gian Paolo มันสั้นชัดเจนและตรวจสอบได้ง่าย / debuggable ในกรณีนี้เราได้มอบหมายรายละเอียดของนิพจน์บูลีนให้กับคอมไพเลอร์


1

คุณไม่ต้องกังวลเกี่ยวกับชุดค่าผสมของแฟล็กบูลีนที่ไม่ถูกต้องหากคุณกำจัดแฟล็กบูลีน

ค่าที่ยอมรับได้คือ:

         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;
    }
}

นี่เป็นรหัสที่มากกว่าในคำตอบของ Gian Paoloแต่ขึ้นอยู่กับสถานการณ์ของคุณสิ่งนี้อาจดูแลรักษาได้มากกว่า:

  • มีชุดฟังก์ชันกลางที่จะปรับเปลี่ยนหากมีการเพิ่มคุณสมบัติหรือสถานการณ์บูลีนเพิ่มเติม
    • การเพิ่มคุณสมบัติจำเป็นต้องเพิ่มเพียงฟังก์ชันเดียว
    • หากเพิ่มสถานการณ์การเปิดใช้งานคำเตือนของคอมไพลเลอร์เกี่ยวกับenumกรณีที่ไม่ได้จัดการในswitchคำสั่งจะตรวจจับตัวรับคุณสมบัติที่ไม่ได้จัดการกับสถานการณ์นั้น
  • หากคุณต้องการแก้ไขคุณสมบัติบูลีนแบบไดนามิกคุณไม่จำเป็นต้องตรวจสอบชุดค่าผสมซ้ำทุกที่ แทนที่จะสลับแฟล็กบูลีนแต่ละรายการ (ซึ่งอาจส่งผลให้ชุดค่าผสมของแฟล็กไม่ถูกต้อง) คุณจะมีเครื่องสถานะที่เปลี่ยนจากสถานการณ์หนึ่งไปเป็นอีกสถานการณ์หนึ่งแทน

วิธีนี้ยังมีประโยชน์อีกด้านของการมีประสิทธิภาพมาก


0

2 เซ็นต์ของฉัน: ประกาศผลรวมตัวแปร (จำนวนเต็ม) ดังนั้น

if(bValue1)
{
  sum=sum+1;
}
if(bValue2)
{
  sum=sum+2;
}
if(bValue3)
{
  sum=sum+4;
}
if(bValue4)
{
  sum=sum+8;
}

ตรวจสอบผลรวมกับเงื่อนไขที่คุณต้องการเท่านี้ก็เสร็จเรียบร้อย ด้วยวิธีนี้คุณสามารถเพิ่มเงื่อนไขต่างๆได้อย่างง่ายดายในอนาคตทำให้อ่านค่อนข้างตรงไปตรงมา


0

คำตอบที่ยอมรับนั้นใช้ได้เมื่อคุณมีเพียง 3 กรณีและตรรกะของแต่ละกรณีนั้นง่ายมาก

แต่ถ้าตรรกะของแต่ละกรณีมีความซับซ้อนมากขึ้นหรือมีกรณีอื่น ๆ อีกมากมายตัวเลือกที่ดีกว่าคือการใช้รูปแบบการออกแบบห่วงโซ่ความรับผิดชอบ

คุณสร้างBaseValidatorซึ่งมีการอ้างอิงถึง a BaseValidatorและวิธีการvalidateและวิธีการในการเรียกใช้การตรวจสอบความถูกต้องบนตัวตรวจสอบที่อ้างอิง

class BaseValidator {
    BaseValidator* nextValidator;

    public:
    BaseValidator() {
        nextValidator = 0;
    }

    void link(BaseValidator validator) {
        if (nextValidator) {
            nextValidator->link(validator);
        } else {
            nextValidator = validator;
        }
    }

    bool callLinkedValidator(bool v1, bool v2, bool v3, bool v4) {
        if (nextValidator) {
            return nextValidator->validate(v1, v2, v3, v4);
        }

        return false;
    }

    virtual bool validate(bool v1, bool v2, bool v3, bool v4) {
        return false;
    }
}

จากนั้นคุณสร้างคลาสย่อยจำนวนหนึ่งที่สืบทอดมาจากการBaseValidatorแทนที่validateเมธอดด้วยตรรกะที่จำเป็นสำหรับตัวตรวจสอบแต่ละตัว

class Validator1: public BaseValidator {
    public:
    bool validate(bool v1, bool v2, bool v3, bool v4) {
        if (v1 && v2 && v3 && v4) {
            return true;
        }

        return nextValidator->callLinkedValidator(v1, v2, v3, v4);
    }
}

จากนั้นใช้มันเป็นเรื่องง่ายสร้างตัวตรวจสอบความถูกต้องของคุณและตั้งค่าแต่ละตัวให้เป็นรูทของผู้อื่น:

Validator1 firstValidator = new Validator1();
Validator2 secondValidator = new Validator2();
Validator3 thirdValidator = new Validator3();
firstValidator.link(secondValidator);
firstValidator.link(thirdValidator);
if (firstValidator.validate(value1, value2, value3, value4)) { ... }

โดยพื้นฐานแล้วกรณีการตรวจสอบความถูกต้องแต่ละกรณีจะมีชั้นของตัวเองซึ่งรับผิดชอบในการ (ก) พิจารณาว่าการตรวจสอบความถูกต้องตรงกับสิ่งนั้นหรือไม่กรณีและ (ข) การตรวจสอบการส่งให้กับบุคคลอื่นในห่วงโซ่ถ้ามันไม่ได้

โปรดทราบว่าฉันไม่คุ้นเคยกับ C ++ ฉันได้พยายามจับคู่ไวยากรณ์จากตัวอย่างบางส่วนที่พบทางออนไลน์แล้ว แต่ถ้าไม่ได้ผลให้ปฏิบัติเหมือนรหัสเทียมมากกว่า ฉันยังมีตัวอย่าง Python ที่ใช้งานได้อย่างสมบูรณ์ด้านล่างซึ่งสามารถใช้เป็นพื้นฐานได้หากต้องการ

class BaseValidator:
    def __init__(self):
        self.nextValidator = 0

    def link(self, validator):
        if (self.nextValidator):
            self.nextValidator.link(validator)
        else:
            self.nextValidator = validator

    def callLinkedValidator(self, v1, v2, v3, v4):
        if (self.nextValidator):
            return self.nextValidator.validate(v1, v2, v3, v4)

        return False

    def validate(self, v1, v2, v3, v4):
        return False

class Validator1(BaseValidator):
    def validate(self, v1, v2, v3, v4):
        if (v1 and v2 and v3 and v4):
            return True
        return self.callLinkedValidator(v1, v2, v3, v4)

class Validator2(BaseValidator):
    def validate(self, v1, v2, v3, v4):
        if (v1 and v2 and v3 and not v4):
            return True
        return self.callLinkedValidator(v1, v2, v3, v4)

class Validator3(BaseValidator):
    def validate(self, v1, v2, v3, v4):
        if (v1 and not v2 and not v3 and not v4):
            return True
        return self.callLinkedValidator(v1, v2, v3, v4)

firstValidator = Validator1()
secondValidator = Validator2()
thirdValidator = Validator3()
firstValidator.link(secondValidator)
firstValidator.link(thirdValidator)
print(firstValidator.validate(False, False, True, False))

อีกครั้งคุณอาจพบว่ามีการใช้งานมากเกินไปสำหรับตัวอย่างเฉพาะของคุณ แต่จะสร้างรหัสที่สะอาดกว่ามากหากคุณจบลงด้วยชุดคดีที่ซับซ้อนมากขึ้นซึ่งจำเป็นต้องพบ


-2

แนวทางง่ายๆคือการค้นหาคำตอบที่คุณคิดว่ายอมรับได้

ใช่ = (boolean1 && boolean2 && boolean3 && boolean4) + + ...

ตอนนี้ถ้าเป็นไปได้ให้ลดความซับซ้อนของสมการโดยใช้พีชคณิตบูลีน

เช่นเดียวกับในกรณีนี้ acceptable1 และ 2 (boolean1 && boolean2 && boolean3)รวมกันเพื่อ

ดังนั้นคำตอบสุดท้ายคือ:

(boolean1 && boolean2 && boolean3) || 
((boolean1 && !boolean2 && !boolean3 && !boolean4)

-3

ใช้ฟิลด์บิต :

unoin {
  struct {
    bool b1: 1;
    bool b2: 1;
    bool b3: 1;
    bool b4: 1;
  } b;
  int i;
} u;

// set:
u.b.b1=true;
...

// test
if (u.i == 0x0f) {...}
if (u.i == 0x0e) {...}
if (u.i == 0x08) {...}

PS :

นั่นเป็นเรื่องน่าเสียดายอย่างยิ่งสำหรับ CPPers ' แต่ UB ไม่กังวลของฉันตรวจสอบที่http://coliru.stacked-crooked.com/a/2b556abfc28574a1


2
สิ่งนี้ทำให้เกิด UB เนื่องจากการเข้าถึงเขตข้อมูลสหภาพที่ไม่ได้ใช้งาน
HolyBlackCat

โดยปกติแล้วมันเป็น UB ใน C ++ คุณไม่สามารถตั้งค่าสมาชิกสหภาพหนึ่งคนและอ่านจากอีกคนหนึ่งได้ ในทางเทคนิคแล้วการใช้ getters \ setters แบบเทมเพลตจะดีกว่าสำหรับบิตของค่าอินทิกรัล
Swift - Friday Pie

ฉันคิดว่าพฤติกรรมจะเปลี่ยนไปใช้ Implementation-Defined หากมีคนแปลงที่อยู่ของสหภาพเป็นunsigned char*แม้ว่าฉันคิดว่าการใช้สิ่งที่ต้องการ((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1น่าจะมีประสิทธิภาพมากกว่า
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.