แยกออกจากวงซ้อนกัน


216

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

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

อะไรคือวิธีที่รวดเร็วและดีที่จะทำเรื่องนี้?

ฉันคิดว่าการยกเว้นจะไม่ถูก / ควรถูกโยนในสภาพที่ยอดเยี่ยมจริงๆเท่านั้นดังนั้นฉันไม่คิดว่าวิธีนี้จะดีในแง่ของประสิทธิภาพ

ฉันไม่รู้สึกว่ามันถูกต้องที่จะใช้ประโยชน์จากฟีเจอร์ใหม่ใน. NET (วิธี anon) เพื่อทำสิ่งที่ค่อนข้างพื้นฐาน


ฉันแค่อยากจะแน่ใจว่า: ทำไมคุณถึงต้องการทำเช่นนี้?
Jon Limjap

3
ทำไมคุณไม่ต้องการใช้บูลีน? มีอะไรผิดปกติกับการทำเช่นนั้น?
แอนโทนี่

ใน VB.net คุณสามารถตัดคำสั่ง try / สุดท้าย (ไม่จับ) รอบจำนวนลูปโดยพลการจากนั้น "exit try" จะออกจากพวกเขาทุกจุด
Brain2000

คำตอบ:


206

ดีgotoแต่ที่น่าเกลียดและไม่ได้เสมอ นอกจากนี้คุณยังสามารถวางลูปเป็นวิธีการ (หรือ anon-method) และใช้returnเพื่อออกกลับไปที่รหัสหลัก

    // goto
    for (int i = 0; i < 100; i++)
    {
        for (int j = 0; j < 100; j++)
        {
            goto Foo; // yeuck!
        }
    }
Foo:
    Console.WriteLine("Hi");

VS:

// anon-method
Action work = delegate
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits anon-method
        }
    }
};
work(); // execute anon-method
Console.WriteLine("Hi");

โปรดทราบว่าใน C # 7 เราควรได้รับ "ฟังก์ชั่นท้องถิ่น" ซึ่ง (ไวยากรณ์ tbd ฯลฯ ) หมายความว่ามันควรจะทำงานเช่น:

// local function (declared **inside** another method)
void Work()
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits local function
        }
    }
};
Work(); // execute local function
Console.WriteLine("Hi");

49
ในสถานการณ์ประเภทนี้ฉันไม่คิดว่าการใช้ goto นั้นแย่กว่าการใช้งานปกติของการหยุดพัก (หลังจากทั้งหมดพวกเขาทั้งสองไม่มีกิ่งก้านสาขาที่ไม่มีเงื่อนไขกับป้ายกำกับ
Greg Beech

37
บางครั้ง goto ชั่วร้ายน้อยกว่าทางเลือก
seanb

7
@BeowulfOF - ตัวแบ่งจะแยกออกจากวงในเท่านั้นไม่ใช่วงด้านในและด้านนอก
เกร็กบีช

36
ไปเองไม่น่าเกลียด สิ่งที่น่าเกลียดคือการใช้ goto ในทางที่ผิดซึ่งส่งผลให้เกิดรหัสสปาเก็ตตี้ การใช้ goto เพื่อแยกวงวนซ้อนกันก็โอเคอย่างสมบูรณ์แบบ นอกจากนี้โปรดทราบว่าการหยุดพักทั้งหมดดำเนินการต่อและย้อนกลับจากมุมมองการเขียนโปรแกรมโครงสร้างแทบจะไม่ดีไปกว่า goto - โดยทั่วไปแล้วมันเป็นสิ่งเดียวกันเพียงในบรรจุภัณฑ์ที่ดีกว่า นั่นเป็นเหตุผลที่ภาษาโครงสร้างที่บริสุทธิ์ (เช่น Pascal ดั้งเดิม) ขาดทั้งสามอย่าง
el.pescado

11
@ el.pescado ถูกต้องสมบูรณ์: ผู้เขียนโค้ดหลายคนเข้าใจผิดว่าgotoเป็นอันตรายต่อ seในขณะที่มันเป็นเครื่องมือที่ถูกต้องในการทำบางสิ่งและเช่นเดียวกับเครื่องมือหลายอย่างอาจถูกนำไปใช้ในทางที่ผิด นี้ความเกลียดชังกับศาสนาgotoคือตรงไปตรงมาค่อนข้างโง่และแน่นอนยกเลิกการทางวิทยาศาสตร์
o0 '

96

การปรับ C # ของวิธีการที่ใช้บ่อยใน C - set value ของตัวแปร loop ภายนอกของเงื่อนไข loop (เช่นสำหรับ loop โดยใช้ตัวแปร int INT_MAX -1มักเป็นตัวเลือกที่ดี):

for (int i = 0; i < 100; i++)
{
    for (int j = 0; j < 100; j++)
    {
        if (exit_condition)
        {
            // cause the outer loop to break:
            // use i = INT_MAX - 1; otherwise i++ == INT_MIN < 100 and loop will continue 
            i = int.MaxValue - 1;
            Console.WriteLine("Hi");
            // break the inner loop
            break;
        }
    }
    // if you have code in outer loop it will execute after break from inner loop    
}

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

วิธีการนี้จะทำงานร่วมกับforและwhileลูป foreachแต่ไม่ทำงานสำหรับ ในกรณีที่foreachคุณจะไม่สามารถเข้าถึงโค้ดของตัวแจงนับที่ซ่อนอยู่ดังนั้นคุณจึงไม่สามารถเปลี่ยนแปลงได้ (และแม้ว่าคุณจะIEnumeratorไม่มีวิธี "MoveToEnd")

กิตติกรรมประกาศสำหรับผู้เขียนความเห็นที่ถูกขีดเส้น ใต้ :
i = INT_MAX - 1คำแนะนำโดยMeta
for / foreachความคิดเห็นโดยygoe
เหมาะสมIntMaxโดยjmbpiano
พูดถึงโค้ดหลังลูปภายในโดยblizpasta


@DrG: จะไม่ทำงานใน c # เนื่องจาก "คำสั่ง break จะยุติการวนรอบที่ใกล้ที่สุดหรือคำสั่ง switch ที่ปรากฏขึ้น" (
msdn

1
@blizpasta งั้นเหรอ? หากเขาทำเงื่อนไขกับลูปด้านนอกเป็นเท็จ (เหมือนที่เขาทำ) มันจะออกจากทั้งสอง
แพทริค

51
คุณควรใช้ i = INT_MAX - 1; มิฉะนั้น i ++ == INT_MIN <100 และวนซ้ำจะยังคงอยู่
Meta

6
ความคิดเกี่ยวกับ foreach ใด ๆ
ktutnik

4
@ktutnik มันจะไม่ทำงานกับ foreach เพราะคุณจะไม่สามารถเข้าถึงโค้ดของ enumerator ที่ซ่อนอยู่ได้ IEnumerator ยังไม่มีวิธี "MoveToEnd"
ygoe

39

วิธีนี้ใช้ไม่ได้กับ C #

สำหรับผู้ที่พบคำถามนี้ผ่านทางภาษาอื่น ๆJavascript, Java, และ D อนุญาตให้มีการแบ่งป้ายกำกับและดำเนินการต่อ :

outer: while(fn1())
{
   while(fn2())
   {
     if(fn3()) continue outer;
     if(fn4()) break outer;
   }
}

40
มันน่าเศร้าที่มันไม่สามารถทำได้ด้วย c # ในหลาย ๆ ครั้งมันจะสร้างโค้ดที่สะอาดขึ้น
Rickard

17
จริง ๆ แล้วฉันรู้สึกตื่นเต้นเป็นครั้งที่สองจนกระทั่งฉันรู้ว่านี่ไม่ใช่สำหรับ c # :(
Arvo Bowen

1
แนวคิดนี้มีไว้สำหรับPowerShellในกรณีที่มีใครบางคนพบปัญหาที่นั่น (พวกเขาใส่เครื่องหมายโคลอนไว้ด้านหน้าชื่อฉลาก) ตอนนี้ฉันรู้แล้วว่านี่ไม่ใช่ PowerShell เฉพาะ ... ไวยากรณ์นี้จะไม่เข้ากันกับป้ายชื่อ goto ที่มีใน C # PHP ใช้อย่างอื่น: break 3; ใส่จำนวนระดับหลังคำสั่ง break
ygoe

1
นี่ไม่ใช่จาวา c #
Luca Ziegler

สำหรับผู้ที่ต้องการดูคุณลักษณะนี้ใน C # ให้วางความรุ่งโรจน์ของคุณที่นี่โปรด😉 github.com/dotnet/csharplang/issues/869#issuecomment-326606003
m93a

28

ใช้ตัวป้องกันที่เหมาะสมในลูปด้านนอก ตั้งยามในวงในก่อนที่จะทำลาย

bool exitedInner = false;

for (int i = 0; i < N && !exitedInner; ++i) {

    .... some outer loop stuff

    for (int j = 0; j < M; ++j) {

        if (sometest) {
            exitedInner = true;
            break;
        }
    }
    if (!exitedInner) {
       ... more outer loop stuff
    }
}

หรือดีกว่าให้นามธรรมวงในเป็นวิธีการและออกจากวงนอกเมื่อมันกลับเท็จ

for (int i = 0; i < N; ++i) {

    .... some outer loop stuff

    if (!doInner(i, N, M)) {
       break;
    }

    ... more outer loop stuff
}

4
ยกเว้น OP กล่าวว่า "ฉันไม่ต้องการใช้บูลีน"
LeopardSkinPillBoxHat

Pascal-ish มาก ... ฉันน่าจะใช้ goto มากกว่า แต่ฉันมักจะหลีกเลี่ยงพวกมันเหมือนโรคระบาด
Jonathan Leffler

21

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

ไปที่:

for ( int i = 0; i < 10; ++i ) {
   for ( int j = 0; j < 10; ++j ) {
      // code
      if ( break_condition ) goto End;
      // more code
   }
}
End: ;

เงื่อนไข:

bool exit = false;
for ( int i = 0; i < 10 && !exit; ++i ) {
   for ( int j = 0; j < 10 && !exit; ++j ) {
      // code
      if ( break_condition ) {
         exit = true;
         break; // or continue
      }
      // more code
   }
}

ข้อยกเว้น:

try {
    for ( int i = 0; i < 10 && !exit; ++i ) {
       for ( int j = 0; j < 10 && !exit; ++j ) {
          // code
          if ( break_condition ) {
             throw new Exception()
          }
          // more code
       }
    }
catch ( Exception e ) {}

2
สิ่งเหล่านี้ล้วนเป็นวิธีแก้ปัญหาแฮ็คที่มันจะสะอาดสุด ๆ เพียงแค่แยกตัวเป็นวิธีการและใช้ผลตอบแทนเร็ว
ดัสตินเก็ตซ์

3
:) ใช่ว่าเป็นวิธีง่ายๆ แต่คุณจะต้องส่งข้อมูลท้องถิ่นที่จำเป็นทั้งหมดไปยังวิธีการเป็นข้อโต้แย้ง ... นี่เป็นหนึ่งในไม่กี่แห่งที่ข้ามไปอาจเป็นทางออกที่เหมาะสม
David Rodríguez - dribeas

วิธีการเงื่อนไขไม่ทำงานแม้กระทั่งเพราะ "โค้ดเพิ่มเติม" จะถูกดำเนินการหนึ่งครั้งหลังจากออกจากลูปด้านในก่อนออกจากลูปภายนอก วิธีการทำงานของ GOTO แต่ทำในสิ่งที่ผู้โพสต์บอกว่าพวกเขาไม่ต้องการทำ วิธีการยกเว้นทำงานได้ไม่ดีเท่าและช้ากว่า GOTO
โปรแกรมเมอร์ Windows

ฉันจะเน้นเครื่องหมายอัฒภาคนั้นหลังฉลาก วิธีนี้ป้ายกำกับอาจอยู่ที่ท้ายบล็อก +1
Tamas Hegedus

@Windowsprogrammer OP ถามว่า "อะไรคือวิธีที่รวดเร็วและดีในการทำเรื่องนี้?" Goto เป็นโซลูชันที่ต้องการ: สะอาดและรัดกุมโดยไม่ต้องใช้วิธีแยกต่างหาก
Suncat2000

16

มันเป็นไปได้ที่จะ refactor ซ้อนกันสำหรับวนเป็นวิธีส่วนตัวหรือไม่ ด้วยวิธีนี้คุณสามารถ 'คืน' ออกจากวิธีการเพื่อออกจากลูป


ด้วยประโยชน์ด้านข้างของการทำให้วิธีการดั้งเดิมของคุณสั้นลง :-)
Martin Capodici

C ++ 11 lambdas ทำให้เป็นเรื่องง่ายสำหรับบางกรณี:[&] { ... return; ... }();
BCS

14

ดูเหมือนว่าฉันจะชอบผู้คนที่ไม่ชอบgotoคำแถลงมากดังนั้นฉันจึงรู้สึกว่าจำเป็นที่จะต้องทำให้เรื่องนี้ตรงออกไปเล็กน้อย

ฉันเชื่อว่าคนที่ 'อารมณ์' มีเกี่ยวกับgotoในที่สุดก็ถึงความเข้าใจในโค้ดและ (ความเข้าใจผิด) เกี่ยวกับความเป็นไปได้ในการใช้งาน ก่อนที่จะตอบคำถามฉันจะเข้าไปดูรายละเอียดเกี่ยวกับวิธีการรวบรวม

อย่างที่เราทราบกันดีว่า C # นั้นถูกคอมไพล์ไปที่ IL ซึ่งจะถูกคอมไพล์ให้กับแอสเซมเบลอร์ด้วย SSA ฉันจะให้ข้อมูลเชิงลึกเกี่ยวกับวิธีการทำงานทั้งหมดนี้แล้วลองตอบคำถามเอง

จาก C # ถึง IL

ก่อนอื่นเราต้องใช้รหัส C # เริ่มง่าย ๆ :

foreach (var item in array)
{
    // ... 
    break;
    // ...
}

ฉันจะทำตามขั้นตอนนี้เพื่อให้คุณมีความคิดที่ดีว่าเกิดอะไรขึ้นภายใต้ประทุน

การแปลครั้งแรก: จากforeachเป็นforลูปที่เทียบเท่า(หมายเหตุ: ฉันกำลังใช้อาร์เรย์ที่นี่เพราะฉันไม่ต้องการทราบรายละเอียดของ IDisposable - ในกรณีนี้ฉันต้องใช้ IEnumerable ด้วย):

for (int i=0; i<array.Length; ++i)
{
    var item = array[i];
    // ...
    break;
    // ...
}

การแปลครั้งที่สอง: forและbreakถูกแปลเป็นภาษาที่เทียบเท่าได้ง่ายขึ้น:

int i=0;
while (i < array.Length)
{
    var item = array[i];
    // ...
    break;
    // ...
    ++i;
}

และการแปลที่สาม (นี้เป็นเทียบเท่าของรหัส IL): เราเปลี่ยนbreakและwhileเข้าไปในสาขา:

    int i=0; // for initialization

startLoop:
    if (i >= array.Length) // for condition
    {
        goto exitLoop;
    }
    var item = array[i];
    // ...
    goto exitLoop; // break
    // ...
    ++i;           // for post-expression
    goto startLoop; 

ในขณะที่คอมไพเลอร์ทำสิ่งเหล่านี้ในขั้นตอนเดียวมันช่วยให้คุณเข้าใจกระบวนการ รหัส IL ที่วิวัฒนาการมาจากโปรแกรม C # เป็นการแปลตามตัวอักษรของรหัส C # ครั้งสุดท้าย คุณสามารถเห็นตัวคุณเองได้ที่นี่: https://dotnetfiddle.net/QaiLRz (คลิก 'ดู IL')

ตอนนี้สิ่งหนึ่งที่คุณสังเกตได้ที่นี่คือในระหว่างกระบวนการรหัสจะซับซ้อนมากขึ้น วิธีที่ง่ายที่สุดในการสังเกตสิ่งนี้คือความจริงที่ว่าเราต้องการรหัสมากขึ้นเพื่อทำสิ่งเดียวกัน นอกจากนี้คุณยังอาจโต้แย้งว่าforeach, for, whileและbreakเป็นจริงสั้นมือสำหรับgotoซึ่งเป็นจริงบางส่วน

จาก IL ถึง Assembler

คอมไพเลอร์. NET JIT เป็นคอมไพเลอร์ SSA ฉันจะไม่เข้าไปดูรายละเอียดทั้งหมดของฟอร์ม SSA ที่นี่และวิธีสร้างคอมไพเลอร์ที่ปรับให้เหมาะสมมันมากเกินไป แต่สามารถให้ความเข้าใจพื้นฐานเกี่ยวกับสิ่งที่จะเกิดขึ้น เพื่อความเข้าใจที่ลึกซึ้งยิ่งขึ้นควรเริ่มต้นอ่านการเพิ่มประสิทธิภาพคอมไพเลอร์ (ฉันชอบหนังสือเล่มนี้เพื่อแนะนำสั้น ๆ : http://ssabook.gforge.inria.fr/latest/book.pdf ) และ LLVM (llvm.org) .

คอมไพเลอร์ปรับให้เหมาะสมทุกครั้งอาศัยความจริงที่ว่ารหัสนั้นง่ายและเป็นไปตามรูปแบบที่สามารถคาดเดาได้ ในกรณีของการวนรอบ FOR เราใช้ทฤษฎีกราฟเพื่อวิเคราะห์สาขาและเพิ่มประสิทธิภาพของ cycli ในสาขาของเรา (เช่นสาขาย้อนหลัง)

อย่างไรก็ตามตอนนี้เรามีสาขาไปข้างหน้าเพื่อใช้ลูปของเรา ตามที่คุณอาจเดาได้จริง ๆ แล้วนี่เป็นหนึ่งในขั้นตอนแรกที่ JIT จะแก้ไขเช่นนี้:

    int i=0; // for initialization

    if (i >= array.Length) // for condition
    {
        goto endOfLoop;
    }

startLoop:
    var item = array[i];
    // ...
    goto endOfLoop; // break
    // ...
    ++i;           // for post-expression

    if (i >= array.Length) // for condition
    {
        goto startLoop;
    }

endOfLoop:
    // ...

อย่างที่คุณเห็นตอนนี้เรามีสาขาย้อนกลับซึ่งก็คือวงเล็ก ๆ ของเรา สิ่งเดียวที่ยังน่ารังเกียจอยู่ที่นี่คือสาขาที่เราลงเอยด้วยbreakคำแถลงของเรา ในบางกรณีเราสามารถย้ายสิ่งนี้ในลักษณะเดียวกัน แต่ในบางกรณีเราสามารถอยู่ได้

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

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

คุณควรตระหนักว่า ณ จุดนี้ความเรียบง่ายforeachสามารถคาดเดาได้มากกว่าจากนั้นเป็นgotoประโยคที่ไปทั่วสถานที่ ในแง่ของ (1) ความสามารถในการอ่านและ (2) จากมุมมองของเครื่องมือเพิ่มประสิทธิภาพมันเป็นทางออกที่ดีกว่า

อีกสิ่งที่มูลค่าการกล่าวขวัญก็คือว่ามันมีความเกี่ยวข้องมากสำหรับการเพิ่มประสิทธิภาพคอมไพเลอร์ที่จะลงทะเบียนกำหนดตัวแปร (กระบวนการที่เรียกว่าการจัดสรรลงทะเบียน ) ดังที่คุณอาจทราบว่ามีจำนวน จำกัด ในการลงทะเบียน CPU ของคุณและพวกเขาเป็นหน่วยความจำที่เร็วที่สุดในฮาร์ดแวร์ของคุณ ตัวแปรที่ใช้ในรหัสที่อยู่ในลูปส่วนใหญ่มีแนวโน้มที่จะได้รับการลงทะเบียนในขณะที่ตัวแปรที่อยู่นอกลูปของคุณมีความสำคัญน้อยกว่า (เพราะรหัสนี้อาจจะตีน้อยกว่า)

ช่วยซับซ้อนเกินไป ... ฉันควรทำอย่างไรดี?

บรรทัดล่างคือคุณควรใช้ภาษาสร้างที่คุณมีในการกำจัดของคุณซึ่งมักจะ (โดยนัย) สร้างรูปแบบที่คาดการณ์ได้สำหรับคอมไพเลอร์ของคุณ พยายามหลีกเลี่ยงการสาขาแปลกถ้าเป็นไปได้ (เฉพาะ: break, continue, gotoหรือreturnในช่วงกลางของอะไร)

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

หนึ่งในรูปแบบเหล่านั้นเรียกว่า SESE ซึ่งย่อมาจาก Single Entry Single Exit

และตอนนี้เรามาถึงคำถามที่แท้จริง

ลองนึกภาพว่าคุณมีสิ่งนี้:

// a is a variable.

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...

     if (i*j > a) 
     {
        // break everything
     }
  }
}

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

int i, j;
for (i=0; i<100 && i*j <= a; ++i) 
{
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
}

ในกรณีอื่นคุณสามารถแยกวิธีเป็น 2 วิธี

// Outer loop in method 1:

for (i=0; i<100 && processInner(i); ++i) 
{
}

private bool processInner(int i)
{
  int j;
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
  return i*j<=a;
}

ตัวแปรชั่วคราว? ดีเลวหรือน่าเกลียด?

คุณอาจตัดสินใจคืนบูลีนจากภายในลูป (แต่โดยส่วนตัวแล้วฉันชอบแบบฟอร์ม SESE เพราะนั่นเป็นวิธีที่คอมไพเลอร์จะเห็นมันและฉันคิดว่ามันอ่านง่ายกว่า)

บางคนคิดว่าการใช้ตัวแปรชั่วคราวนั้นสะอาดกว่าและเสนอวิธีแก้ปัญหาเช่นนี้:

bool more = true;
for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j) 
  {
     // ...
     if (i*j > a) { more = false; break; } // yuck.
     // ...
  }
  if (!more) { break; } // yuck.
  // ...
}
// ...

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

ใช่ให้ฉันสะกดมันออกมา จะเกิดอะไรขึ้นคือ:

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

ดังนั้นเพื่อสรุป: เครื่องมือเพิ่มประสิทธิภาพในคอมไพเลอร์ของคุณจะเข้าสู่นรกของปัญหามากมายที่จะคิดออกว่าmoreจะใช้สำหรับโฟลว์การควบคุมเท่านั้นและในกรณีสถานการณ์ที่ดีที่สุดจะแปลเป็นสาขาเดียวนอกด้านนอกสำหรับ ห่วง

กล่าวอีกนัยหนึ่งสถานการณ์กรณีที่ดีที่สุดคือมันจะจบลงด้วยสิ่งนี้:

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...
     if (i*j > a) { goto exitLoop; } // perhaps add a comment
     // ...
  }
  // ...
}
exitLoop:

// ...

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

TL; DR:

บรรทัดล่างสุด:

  • ใช้เงื่อนไขง่ายๆในการวนรอบถ้าเป็นไปได้ ยึดติดกับโครงสร้างภาษาระดับสูงที่คุณมีให้มากที่สุด
  • หากทุกอย่างล้มเหลวและคุณจะเหลืออย่างใดอย่างหนึ่งgotoหรือbool moreชอบอดีต

OTOH: ฉันไม่ค่อยมีความปรารถนาที่จะทำลายลูปนอกหรือต้องการ goto-equiv (และโค้ดจำนวนมากที่สามารถเขียนได้อย่างหมดจดมากขึ้น) แม้ว่ามันอาจจะพบได้บ่อยในโดเมนอื่น .. วันนี้เป็นกรณีเช่นนี้ yield returnแต่นั่นก็เป็นส่วนใหญ่เนื่องจากการ นั่นคือในขณะที่มันง่ายที่จะแสดงว่ามีบางกรณีที่มีประโยชน์สำหรับรหัส "ระดับแอปพลิเคชัน" ส่วนใหญ่อาจเป็นเรื่องที่เกิดขึ้นน้อยมากกับ C # ยกเว้นกรณีที่ "เพิ่งมาจาก" C / C ++ ;-) บางครั้ง "การขว้าง" ของ Ruby's (ไม่รวมข้อยกเว้น) ในบางโอกาสเนื่องจากมันเหมาะกับโดเมนนี้
user2864740

1
@ Suncat2000 เพียงแค่แนะนำรหัสเพิ่มเติมเช่นตัวแปรและสาขาที่มีเงื่อนไขเพื่อหลีกเลี่ยงการใช้gotoไม่ทำให้โค้ดของคุณอ่านง่ายขึ้น - ฉันเถียงว่าเกิดอะไรขึ้นตรงกันข้าม: ในความเป็นจริงโฟลว์การควบคุมของคุณจะมีโหนดมากขึ้น นิยามของความซับซ้อน เพียงหลีกเลี่ยงเพื่อช่วยให้มีระเบียบวินัยในตัวเองฟังดูมีประสิทธิผลในด้านการอ่าน
atlaste

@atlaste: ขออภัยให้ฉันระบุความคิดเห็นของฉันชัดเจนยิ่งขึ้น สิ่งที่ฉันควรจะพูดคือ: คำอธิบายที่ดี แต่ผู้คนที่หลีกเลี่ยง goto ไม่ได้เกี่ยวกับประสิทธิภาพ มันเกี่ยวกับการหลีกเลี่ยงการละเมิดที่ทำให้ขาดความสามารถในการอ่าน เราสามารถสันนิษฐานได้ว่าคนที่หลีกเลี่ยงgotoเพียงแค่ไม่มีระเบียบวินัยที่จะไม่ละเมิด
Suncat2000

@atlaste ภาษาอะไรคือ "ackomplish"
Pacerier

11

ปัจจัยเป็นฟังก์ชั่น / วิธีการและใช้ผลตอบแทนเร็วหรือจัดเรียงลูปของคุณเป็นประโยค กลับไป / ข้อยกเว้น / อะไรก็ตามที่ไม่เหมาะสมที่นี่อย่างแน่นอน

def do_until_equal():
  foreach a:
    foreach b:
      if a==b: return

10

คุณถามหาการผสมผสานระหว่างความรวดเร็วดีและไม่ใช้บูลีนไม่มีการใช้ goto และ C # คุณได้กำจัดวิธีที่เป็นไปได้ทั้งหมดในการทำสิ่งที่คุณต้องการ

วิธีที่รวดเร็วและน่าเกลียดที่สุดคือใช้ goto


เห็นด้วยอย่างสิ้นเชิง. แนะนำวิธีการใหม่ในการกำจัด goto เพียงครั้งเดียวนั้นโง่ หากคอมไพเลอร์ไม่สามารถอินไลน์วิธีการนั้นได้ด้วยเหตุผลใดก็ตามเราจะจบลงด้วยค่าใช้จ่ายที่ไม่จำเป็นโดยสิ้นเชิงจากการเรียกใช้เมธอดพิเศษ การขว้างและจับข้อยกเว้นเพียงเพื่อทำลายวงนั้นมีทั้งความละเอียดและราคาแพงกว่าอย่างน่าขัน
Zar Shardan

@Windowsprogramm: OP ไม่ขอ "ไม่ใช้ goto" เขาไม่ต้องการที่จะ "ไปที่วิธีอื่น" คำถามอยู่ไกลจากการพิจารณาวิธีการที่เป็นไปได้ทั้งหมดแต่คุณถูกต้องที่จะบอกว่าข้ามไปได้ดีที่สุด
Suncat2000

5

บางครั้งการเขียนโค้ดลงในฟังก์ชั่นของตัวเองเป็นเรื่องที่ดีและใช้ผลตอบแทนเร็วกว่า - การรับคืนเร็วเป็นสิ่งที่เลวร้ายแม้ว่า:)

public void GetIndexOf(Transform transform, out int outX, out int outY)
{
    outX = -1;
    outY = -1;

    for (int x = 0; x < Columns.Length; x++)
    {
        var column = Columns[x];

        for (int y = 0; y < column.Transforms.Length; y++)
        {
            if(column.Transforms[y] == transform)
            {
                outX = x;
                outY = y;

                return;
            }
        }
    }
}

1
แต่วิธีนี้จะช่วยให้วิธีการแก้ปัญหาแบบดั้งเดิมถามโดย OP
Surya Pratap

2

ฉันเคยเห็นตัวอย่างมากมายที่ใช้ "break" แต่ไม่มีใครที่ใช้ "ดำเนินการต่อ"

มันยังคงต้องการแฟล็กของการเรียงลำดับบางอย่างในลูปภายใน:

while( some_condition )
{
    // outer loop stuff
    ...

    bool get_out = false;
    for(...)
    {
        // inner loop stuff
        ...

        get_out = true;
        break;
    }

    if( get_out )
    {
        some_condition=false;
        continue;
    }

    // more out loop stuff
    ...

}

1

ตั้งแต่ฉันแรกเห็นbreakใน C สองสามทศวรรษหลังปัญหานี้มีปัญหาฉัน ฉันหวังว่าการเพิ่มประสิทธิภาพของภาษาจะมีส่วนขยายให้หยุดซึ่งจะทำให้:

break; // our trusty friend, breaks out of current looping construct.
break 2; // breaks out of the current and it's parent looping construct.
break 3; // breaks out of 3 looping constructs.
break all; // totally decimates any looping constructs in force.

3
จากนั้นโปรแกรมเมอร์บำรุงรักษาจะแทรกอีกระดับของการซ้อนจะแก้ไขคำสั่งการแบ่งบางส่วนและจะแบ่งคำสั่งการแบ่งบางส่วน การแก้ไขที่จะแบ่งเป็นป้ายชื่อแทน ได้รับการเสนอชื่อจริง แต่นักปฏิบัตินิยมใช้ฉลากแทน
โปรแกรมเมอร์ Windows

เดี๋ยวก่อนใครจะเขียนโปรแกรมบำรุงรักษาอีกต่อไป :)
Jesse C. Slicer

2
จาวาสคริปต์ยังมีข้อความแจ้งว่าบล็อก / ตัวแบ่ง devguru.com/Technologies/ecmascript/quickref/break.html
David Grant

@Chris Bartow: เจ๋ง! ทำคริสมาสต์ของฉัน :) @ David Grant: ดูเหมือน JS break == C ของ goto?
Jesse C. Slicer

D มีป้ายกำกับ break / continue
BCS

0

ฉันจำได้จากวันที่นักเรียนบอกว่ามันพิสูจน์ได้ทางคณิตศาสตร์ว่าคุณสามารถทำอะไรในโค้ดโดยไม่ต้องใช้ goto (เช่นไม่มีสถานการณ์ที่ goto เป็นคำตอบเดียว) ดังนั้นฉันไม่เคยใช้ goto (แค่ความชอบส่วนตัวไม่แนะนำว่าฉันถูกหรือผิด)

อย่างไรก็ตามเพื่อแยกออกจากลูปซ้อนกันฉันทำอะไรเช่นนี้:

var isDone = false;
for (var x in collectionX) {
    for (var y in collectionY) {
        for (var z in collectionZ) {
            if (conditionMet) {
                // some code
                isDone = true;
            }
            if (isDone)
                break;
        }
        if (isDone) 
            break;
    }
    if (isDone)
        break;
}

... ฉันหวังว่าจะช่วยให้คนที่ชอบฉันต่อต้าน antio goto "fanboys" :)


ขออภัยที่จะบอกคุณ แต่อาจารย์ของคุณเป็นคนที่ตำหนิสภาพของคุณ หากเขาต้องการที่จะบังคับให้คุณเรียนรู้การชุมนุมคุณก็รู้ว่า 'goto' เป็นเพียงการกระโดด (แน่นอนว่าฉันเพิกเฉยต่อข้อเท็จจริงที่ว่านี่คือคำถาม #)
cmroanirgo

0

นั่นเป็นวิธีที่ฉันทำ ยังคงเป็นวิธีแก้ปัญหา

foreach (var substring in substrings) {
  //To be used to break from 1st loop.
  int breaker=1;
  foreach (char c in substring) {
    if (char.IsLetter(c)) {
      Console.WriteLine(line.IndexOf(c));
      \\setting condition to break from 1st loop.
      breaker=9;
      break;
    }
  }
  if (breaker==9) {
    break;
  }
}


0

วิธีที่ง่ายที่สุดในการสิ้นสุดการวนซ้ำสองครั้งจะเป็นการสิ้นสุดการวนซ้ำแรกโดยตรง

string TestStr = "The frog jumped over the hill";
char[] KillChar = {'w', 'l'};

for(int i = 0; i < TestStr.Length; i++)
{
    for(int E = 0; E < KillChar.Length; E++)
    {
        if(KillChar[E] == TestStr[i])
        {
            i = TestStr.Length; //Ends First Loop
            break; //Ends Second Loop
        }
    }
}

วิธีนี้ใช้ไม่ได้ผลโดยใช้ break คุณจะจบ loop ภายใน ลูปด้านนอกจะยังคงวนซ้ำ
Alex Leo

0

ลูปสามารถแตกได้โดยใช้เงื่อนไขที่กำหนดเองในลูปทำให้มีโค้ดที่ชัดเจน

    static void Main(string[] args)
    {
        bool isBreak = false;
        for (int i = 0; ConditionLoop(isBreak, i, 500); i++)
        {
            Console.WriteLine($"External loop iteration {i}");
            for (int j = 0; ConditionLoop(isBreak, j, 500); j++)
            {
                Console.WriteLine($"Inner loop iteration {j}");

                // This code is only to produce the break.
                if (j > 3)
                {
                    isBreak = true;
                }                  
            }

            Console.WriteLine("The code after the inner loop will be executed when breaks");
        }

        Console.ReadKey();
    }

    private static bool ConditionLoop(bool isBreak, int i, int maxIterations) => i < maxIterations && !isBreak;   

ด้วยรหัสนี้เราได้ผลลัพธ์ต่อไปนี้:

  • วนซ้ำภายนอก 0
  • การวนซ้ำภายในวง 0
  • การวนซ้ำภายในวงใน 1
  • การวนซ้ำภายในวง 2
  • การวนซ้ำภายในวง 3
  • การวนซ้ำภายในวง 4
  • รหัสหลังจากวนรอบด้านในจะถูกดำเนินการเมื่อหยุดพัก

0

ตัวเลือกอื่น ๆ ที่เป็นตัวเองเรียกฟังก์ชั่นที่ไม่ระบุชื่อ ไม่มีการข้ามไปไม่มีป้ายกำกับไม่มีตัวแปรใหม่ไม่ใช้ชื่อฟังก์ชันใหม่และสั้นกว่าหนึ่งบรรทัดตัวอย่างเมธอดแบบไม่ระบุชื่อที่ด้านบน

// self-invoked-anonymous-function
new Action(() =>
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits self invoked lambda expression
        }
    }
})();
Console.WriteLine("Hi");

-3

โยนข้อยกเว้นที่กำหนดเองซึ่งออกไปนอกวง

การทำงานสำหรับfor, foreachหรือwhileหรือชนิดใด ๆ ของวงและภาษาที่ใช้try catch exceptionบล็อก

try 
{
   foreach (object o in list)
   {
      foreach (object another in otherList)
      {
         // ... some stuff here
         if (condition)
         {
            throw new CustomExcpetion();
         }
      }
   }
}
catch (CustomException)
{
   // log 
}

-4
         bool breakInnerLoop=false
        for(int i=0;i<=10;i++)
        {
          for(int J=0;i<=10;i++)
          {
              if(i<=j)
                {
                    breakInnerLoop=true;
                    break;
                }
          }
            if(breakInnerLoop)
            {
            continue
            }
        }

ความแตกต่างที่สำคัญกับคำตอบของ dviljoen คืออะไร?
Gert Arnold

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

-4

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

            for (; j < 10; j++)
            {
                //solution
                bool breakme = false;
                for (int k = 1; k < 10; k++)
                {
                   //place the condition where you want to stop it
                    if ()
                    {
                        breakme = true;
                        break;
                    }
                }

                if(breakme)
                    break;
               }

เรียบง่ายและเรียบง่าย :)


อ่านคำถามก่อนแนะนำการพัก ดังนั้น downvotes
cmroanirgo

1
อ่านตอนนี้ แต่เมื่อฉันโพสต์คำตอบนี้รุ่นแก้ไขไม่ใช้บูลีนไม่ได้เพิ่มนี่คือเหตุผลที่ฉันโพสต์คำตอบนี้ .. แต่ขอบคุณสำหรับ downvote!
Steve

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

-6

คุณดูbreakคำหลักด้วยหรือไม่ Oo

นี่เป็นเพียงโค้ดหลอก แต่คุณควรจะเห็นสิ่งที่ฉันหมายถึง:

<?php
for(...) {
    while(...) {
        foreach(...) {
            break 3;
        }
    }
}

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

คู่มือ: http://php.net/break


13
ไม่ใช่คำถาม PHP
Zerga

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.