ถ้าเงื่อนไข A ตรงกันเงื่อนไข B ต้องถูกจับคู่เพื่อดำเนินการ C


148

คำถามของฉันคือ:

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

เป็นไปได้ไหมที่จะเขียนโค้ดของการกระทำ C ครั้งเดียวแทนที่จะเป็นสองครั้ง?

จะทำให้มันง่ายขึ้นได้อย่างไร?


56
ใส่รหัสสำหรับ "action C" ในฟังก์ชั่นหรือไม่?
CinCout

26
นี่เป็นเรื่องน่าเศร้าที่เรื่องนี้ไม่เกี่ยวข้องกับคำถามC ++ได้ที่ HNQ: /
YSC

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

13
นั่นเป็นกลยุทธ์ที่ดีมาก: เขียนโค้ดที่ใช้งานได้ก่อนแล้วจึงกังวลว่าจะทำให้มันดูดีและมีประสิทธิภาพในภายหลัง
Code-Apprentice

3
@ เวลาฉันโพสต์เป็นคำตอบ ในหมายเหตุด้านมันเป็นเรื่องน่าเศร้าที่เห็นคะแนนน้อยลง
CinCout

คำตอบ:


400

ขั้นตอนแรกของปัญหาเหล่านี้คือการสร้างตารางตรรกะ

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

เมื่อคุณจัดทำตารางวิธีการแก้ไขก็ชัดเจน

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

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


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

14
หากการประเมินBมีผลข้างเคียงตารางตรรกะต้องคำนึงถึงสิ่งนั้น
Yakk - Adam Nevraumont

79
@Yakk คำตอบของฉันไม่ได้แก้ไขผลข้างเคียงด้วยเหตุผลสองประการ ก่อนวิธีแก้ปัญหา (บังเอิญ) มีพฤติกรรมผลข้างเคียงที่ถูกต้อง ประการที่สองและที่สำคัญกว่า A และ B ที่มีผลข้างเคียงจะเป็นรหัสที่ไม่ดีและการอภิปรายเกี่ยวกับกรณีที่ขอบจะเป็นสิ่งที่ทำให้ไขว้เขวสำหรับคำถามพื้นฐานเกี่ยวกับตรรกะบูลีน
QuestionC

52
บางทีน่าสังเกตในกรณีที่A && !Bเป็นไม่มี -op: !(A && !B)เทียบเท่า!A || Bซึ่งหมายความว่าคุณสามารถทำได้if (!A || B) { /* do action C */ }และหลีกเลี่ยงการบล็อกว่างเปล่า
KRyan

54
หากif (A && !B)เป็นเรื่องยากสำหรับนักเขียนโปรแกรมในอนาคตที่จะดูแลรักษามันก็ไม่มีทางช่วยพวกเขาได้จริงๆ
Ray

65

คุณมีสองทางเลือก:

  1. เขียนฟังก์ชั่นที่ทำงาน "action C"

  2. จัดเรียงตรรกะของคุณใหม่เพื่อให้คุณไม่มีคำสั่งซ้อนกันมากมาย ถามตัวเองว่าเงื่อนไขใดทำให้ "การกระทำ C" เกิดขึ้น ดูเหมือนว่าฉันจะเกิดขึ้นเมื่อ "เงื่อนไข B" เป็นจริงหรือ "เงื่อนไข A" เป็นเท็จ เราสามารถเขียนสิ่งนี้เป็น "NOT A หรือ B" แปลสิ่งนี้เป็นรหัส C เราจะได้รับ

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

หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับการแสดงออกประเภทนี้ฉันขอแนะนำ googling "พีชคณิตแบบบูล", "ตรรกะภาคแสดง" และ "ภาควิชาแคลคูลัส" เหล่านี้เป็นหัวข้อทางคณิตศาสตร์ลึก คุณไม่จำเป็นต้องเรียนรู้ทั้งหมดเพียงแค่พื้นฐาน

คุณควรเรียนรู้เกี่ยวกับ "การประเมินการลัดวงจร" ด้วยเหตุนี้ลำดับของนิพจน์จึงเป็นสิ่งสำคัญในการทำซ้ำตรรกะดั้งเดิมของคุณ ในขณะที่B || !Aเทียบเท่าเหตุผลใช้นี้เป็นเงื่อนไขที่จะดำเนินการ "การกระทำ C" เมื่อเป็นความจริงโดยไม่คำนึงถึงความคุ้มค่าของBA


15
@Yakk ดูกฎหมายของ deMorgan
รหัส - ผู้ฝึกงาน

@ Code-Apprentice โปรดยกโทษให้ฉันด้วยความคิดที่ไม่ดี ฉันต้องการถามว่ามีความแตกต่างระหว่าง (! A || B) และ (A&&! B) หรือไม่ ดูเหมือนว่าทั้งสองจะโอเคสำหรับปัญหาของฉัน ฉันหมายถึงของคุณและแนวทางของ QuestionC
starf15h

6
@ Starf15h มีความแตกต่างที่สำคัญอีกประการหนึ่งคือ: ที่ "การดำเนินการ C" จะถูกดำเนินการ ความแตกต่างนี้ทำให้โซลูชันทั้งสองของเราเทียบเท่ากันอย่างแท้จริง ฉันขอแนะนำให้คุณ google "deMorgan's Laws" ซึ่งจะช่วยให้คุณเข้าใจสิ่งที่เกิดขึ้นที่นี่
รหัส - ผู้ฝึกงาน

5
ทั้งสองโซลูชั่นเทียบเท่าว่า แต่อาจจะมีความแตกต่างในทางปฏิบัติขึ้นอยู่กับสิ่งที่...เป็น ถ้ามันเป็นอะไรที่ทุกคน (เช่น“ทำ C ถ้าเงื่อนไขเหล่านี้จะได้พบกับมิฉะนั้นทำอะไร”) แล้วนี้เห็นได้ชัดว่าเป็นทางออกที่ดีกว่าเนื่องจากelseคำสั่งก็สามารถจะปล่อยออกมาทั้งหมดแล้ว
Janus Bahs Jacquet

1
ยิ่งไปกว่านั้นขึ้นอยู่กับชื่อของ A และ B ข้อตกลงนี้อาจอ่านได้ง่ายกว่ามนุษย์หรือสามารถอ่านได้น้อยกว่าการจัดเรียงของคำถาม
ไมเคิล - Clay Shirky อยู่ที่ไหน

15

คุณสามารถทำให้คำสั่งนี้ง่ายขึ้น:

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

มิฉะนั้นใส่รหัสสำหรับ 'C' ในฟังก์ชั่นที่แยกต่างหากและเรียกมันว่า:

DoActionC()
{
    ....
    // code for Action C
}
if (condition A)
{
    if(condition B)
      {
         DoActionC(); // call the function
      }
    else
      ...
}
else
{
   DoActionC(); // call the function
}

7
หรือมากกว่านั้นง่ายๆif (!A || B)
Tas

2
มีเหตุผล ((A&&)) ||! A) เทียบเท่ากับ (B ||! A)
ผู้ฝึกปฏิบัติงานรหัส

@ Code-Apprentice B || !Aจะส่งผลให้trueถ้าBเป็นtrueโดยไม่ต้องตรวจสอบจริง ๆAเนื่องจากการลัดวงจร
CinCout

1
@CinCout จุดดี ในขณะที่คำพูดของฉันยังคงเป็นจริงจากมุมมองตรรกะบูลีนเชิงทฤษฎีฉันไม่ได้คำนึงถึงการปฏิบัติของผู้ประกอบการบูลีนลัดวงจร โชคดีที่คำตอบของฉันมีลำดับที่ถูกต้อง
รหัส - ฝึกงาน

1
ดังนั้นจากมุมมองเชิงตรรกะลำดับไม่สำคัญ อย่างไรก็ตามจากมุมมองการบำรุงรักษาและความสามารถในการอ่านอาจมีความแตกต่างกันมากขึ้นกับสิ่งที่แน่นอนAและBยืนหยัด
รหัส - ผู้ฝึกงาน

14

ในภาษาที่มีการจับคู่รูปแบบคุณสามารถแสดงวิธีการแก้ปัญหาที่สะท้อนถึงตารางความจริงโดยตรงในคำตอบของ QuestionC

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

หากคุณไม่คุ้นเคยกับไวยากรณ์แต่ละรูปแบบจะถูกแทนด้วย | ตามด้วยค่าที่จะจับคู่กับ (a, b) และเครื่องหมายขีดล่างจะใช้เป็นอักขระตัวแทนเพื่อหมายถึง "ค่าอื่น ๆ " เนื่องจากกรณีเดียวที่เราต้องการทำสิ่งอื่นนอกเหนือจากการกระทำ c คือเมื่อ a เป็นจริงและ b เป็นเท็จเราจึงกล่าวอย่างชัดเจนว่าค่าเหล่านั้นเป็นรูปแบบแรก (จริงเท็จ) แล้วทำทุกอย่างที่ควรทำในกรณีนั้น ในกรณีอื่น ๆ ทั้งหมดเราอยู่ในรูปแบบ "ตัวแทน" และดำเนินการ c


10

คำแถลงปัญหา:

ถ้าเงื่อนไข A ตรงกันเงื่อนไข B ต้องถูกจับคู่เพื่อดำเนินการ C

อธิบายความหมาย : AหมายถึงBข้อเสนอเชิงตรรกะเทียบเท่ากับ!A || B(ดังกล่าวในคำตอบอื่น ๆ ):

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

if (implies(/* condition A */,
            /* condition B */))
{
    /* do action C */
}

บางทีทำเครื่องหมายว่าinlineเป็น C และconstexprเช่นกันสำหรับ C ++?
einpoklum

@ einpoklum ฉันไม่ได้รับรายละเอียดบางอย่างเพราะคำถามนี้ไม่ได้ระบุภาษา (แต่ยกตัวอย่างด้วยไวยากรณ์ C-like) ดังนั้นฉันจึงตอบด้วยไวยากรณ์ C-like โดยส่วนตัวแล้วฉันจะใช้แมโครเพื่อไม่ให้ประเมินเงื่อนไข B โดยไม่จำเป็น
jamesdlin

6

อืมสิ่งนี้ทำให้ผมสะดุดเช่นกัน แต่เมื่อชี้ให้เห็นโดย Code-Apprenticeเรารับประกันได้ว่าจะต้องdo action Cใช้หรือเรียกใช้elseบล็อกซ้อนกันดังนั้นโค้ดอาจจะง่ายขึ้นเพื่อ:

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

นี่คือวิธีที่เรากดปุ่มทั้ง 3 กรณี:

  1. ซ้อนdo action Cในตรรกะคำถามของคุณที่จำเป็นcondition Aและcondition Bจะเป็นtrue- ในตรรกะนี้ถ้าเราไปถึง 2 ครั้งในระยะif-statement แล้วเรารู้ว่าcondition Aเป็นtrueดังนั้นทั้งหมดที่เราต้องประเมินว่าcondition Bเป็นtrue
  2. else-block ที่ซ้อนกันในตรรกะของคำถามของคุณจำเป็นต้องcondition Aเป็นtrueและcondition Bเป็นfalse- วิธีเดียวที่เราสามารถเข้าถึงelse-block ในตรรกะนี้จะเป็นถ้าcondition Aเป็นtrueและcondition Bถูกfalse
  3. ด้านนอกelse-block ในตรรกะคำถามของคุณที่จำเป็นcondition Aจะต้องมีfalse- ในตรรกะนี้ถ้าcondition Aเป็นเท็จเรายังdo action C

อุปกรณ์ประกอบฉากสู่ Code-Apprentice เพื่อยืดผมออกจากที่นี่ ฉันขอแนะนำให้ยอมรับคำตอบของเขาเนื่องจากเขาแสดงให้ถูกต้องโดยไม่มีการแก้ไข: /


2
โปรดทราบว่า "เงื่อนไข A" ไม่จำเป็นต้องได้รับการประเมินอีกครั้ง ใน C ++ เรามีกฎหมายของคนกลางที่ไม่รวม หาก "ไม่ใช่เงื่อนไข A" เป็นเท็จดังนั้น "เงื่อนไข A" จึงจำเป็นต้องเป็นจริง
รหัส - ฝึกงาน

1
เนื่องจากการประเมินผลการลัดวงจรBจะได้รับการประเมินหาก!Aเป็นเท็จ ดังนั้นทั้งสองจะต้องล้มเหลวในการดำเนินการตามelseคำสั่ง
รหัส - ฝึกงาน

แม้จะไม่มีการประเมินการลัดวงจร!A || Bเป็นเท็จแน่นอนทั้งเมื่อ!AและBเป็นเท็จ ดังนั้นAจะเป็นจริงเมื่อมีการelseดำเนินการ Aไม่จำเป็นที่จะประเมิน
Code-Apprentice

@ Code-Apprentice เหม็นดีสังเกตดีฉันได้แก้ไขคำตอบของฉัน แต่แนะนำให้คุณได้รับการยอมรับ ฉันแค่พยายามอธิบายสิ่งที่คุณนำมาแล้ว
Jonathan Mee

ฉันหวังว่าฉันจะให้คุณอีกคะแนนสำหรับการอธิบายในแต่ละกรณี
รหัส - ผู้ฝึกงาน

6

ในแนวคิดตรรกะคุณสามารถแก้ปัญหานี้ได้ดังนี้

f = ab +! a
f =?

f = !a + bในฐานะที่เป็นปัญหาการพิสูจน์ผลนี้ มีวิธีการพิสูจน์ปัญหาเช่นตารางความจริงแผนที่ Karnaughและอื่น ๆ

ดังนั้นในภาษาที่ใช้ C คุณสามารถใช้ดังต่อไปนี้:

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

PS: Karnaugh Mapใช้สำหรับเงื่อนไขที่ซับซ้อนมากขึ้นเช่นกัน มันเป็นวิธีการลดความซับซ้อนของพีชคณิตแบบบูล


6

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

สิ่งแรกที่คุณต้องการทำคือดูภายใต้เงื่อนไขที่คุณต้องการดำเนินการ C. กรณีนี้คือเมื่อ(a & b)ใด !aนอกจากนี้เมื่อ (a & b) | !aดังนั้นคุณต้อง

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

(a & b) | !a = (a | !a) & (b | !a). a | ! b | !aเป็นจริงเสมอดังนั้นคุณก็สามารถข้ามมันออกมาซึ่งใบคุณมีผลลดลงไปนี้: ในกรณีที่คำสั่งซื้อสร้างความแตกต่างเนื่องจากคุณต้องการตรวจสอบ b เฉพาะในกรณีที่! a เป็นจริง (เช่นเมื่อ! a คือการตรวจสอบ nullpointer และ b เป็นการดำเนินการบนตัวชี้เช่น @ LordFarquaad ชี้ให้เห็นในความคิดเห็นของเขา) คุณอาจ ต้องการสลับทั้งสอง

อีกกรณีหนึ่ง (/ * ... * /) จะถูกดำเนินการเสมอเมื่อไม่ดำเนินการ c ดังนั้นเราสามารถใส่ไว้ในอีกกรณีหนึ่งได้

ด้วยมูลค่าการกล่าวขวัญว่ามันอาจทำให้รู้สึกทั้งสองวิธีที่จะนำการกระทำ c เป็นวิธีการ

ซึ่งทำให้เรามีรหัสต่อไปนี้:

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

วิธีนี้คุณยังสามารถลดข้อกำหนดด้วยตัวถูกดำเนินการมากขึ้นซึ่งจะน่าเกลียดด้วยตารางความจริงอย่างรวดเร็ว อีกวิธีที่ดีคือแผนที่ Karnaugh แต่ตอนนี้ฉันจะไม่ลงลึกไปกว่านี้


4

ในการทำให้โค้ดดูเหมือนข้อความให้ใช้แฟล็กบูลีน หากตรรกะไม่ชัดเจนโดยเฉพาะให้เพิ่มความคิดเห็น

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


2

ฉันจะแยก C เป็นวิธีแล้วออกจากฟังก์ชันโดยเร็วที่สุดในทุกกรณี elseส่วนคำสั่งท้ายสุดควรกลับด้านถ้าเป็นไปได้ นี่คือตัวอย่างทีละขั้นตอน:

ดึง C:

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

ย้อนกลับifไปก่อนเพื่อกำจัดสิ่งแรกelse:

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

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

กำจัดวินาทีelse:

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

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

D();

จากนั้นคุณสามารถสังเกตเห็นว่าทั้งสองกรณีมีเนื้อหาเหมือนกันและสามารถรวมกันได้:

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

D();

สิ่งที่เป็นทางเลือกในการปรับปรุงคือ:

  • ขึ้นอยู่กับบริบท แต่หาก!A || Bสับสนให้แตกออกเป็นหนึ่งหรือหลายตัวแปรเพื่ออธิบายเจตนา

  • ไม่ว่ากรณีใด ๆC()หรือD()เป็นกรณีที่ไม่ได้รับการยกเว้นควรดำเนินต่อไปดังนั้นหากD()เป็นข้อยกเว้นให้สลับifหนึ่งครั้งเป็นครั้งสุดท้าย


2

การใช้แฟล็กยังสามารถแก้ปัญหานี้ได้

int flag = 1; 
if ( condition A ) {
    flag = 2;
    if( condition B ) {
        flag = 3;
    }
}
if(flag != 2) { 
    do action C 
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.