Thoát ra khỏi một vòng lặp lồng nhau


216

Nếu tôi có một vòng lặp for được lồng trong một vòng lặp khác, làm thế nào tôi có thể đi ra một cách hiệu quả cả hai vòng lặp (bên trong và bên ngoài) một cách nhanh nhất có thể?

Tôi không muốn phải sử dụng một boolean và sau đó phải nói đi đến một phương thức khác, mà chỉ để thực thi dòng mã đầu tiên sau vòng lặp bên ngoài.

Một cách nhanh chóng và tốt đẹp về việc này là gì?

Tôi đã nghĩ rằng các ngoại lệ không rẻ / chỉ nên được ném trong một điều kiện thực sự đặc biệt, v.v. Do đó tôi không nghĩ giải pháp này sẽ tốt từ góc độ hiệu suất.

Tôi không cảm thấy việc tận dụng các tính năng mới hơn trong .NET (phương pháp anon) là đúng đắn để làm một cái gì đó khá cơ bản.


Tôi chỉ muốn chắc chắn: tại sao bạn muốn làm điều này?
Jon Limjap

3
Tại sao bạn không muốn sử dụng boolean? Có gì sai khi làm điều đó?
Anthony

Trong VB.net, bạn có thể gói một câu lệnh thử / cuối cùng (không bắt) xung quanh một số vòng lặp tùy ý, sau đó "thoát thử" sẽ thoát tất cả chúng tại bất kỳ điểm nào.
Brain2000

Câu trả lời:


206

Chà, gotonhưng điều đó thật xấu xí, và không phải lúc nào cũng có thể. Bạn cũng có thể đặt các vòng lặp vào một phương thức (hoặc một phương thức anon) và sử dụng returnđể thoát trở lại mã chính.

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

Lưu ý rằng trong C # 7, chúng ta sẽ nhận được "các hàm cục bộ", điều này (cú pháp tbd, v.v.) có nghĩa là nó sẽ hoạt động giống như:

// 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
Trong tình huống này, tôi không nghĩ việc sử dụng goto là tồi tệ hơn việc sử dụng bình thường một thứ gì đó như phá vỡ (sau tất cả chúng chỉ là các nhánh vô điều kiện cho một nhãn, chỉ có điều đó là phá vỡ nhãn là ẩn).
Greg Beech

37
đôi khi goto ít ác hơn so với các lựa chọn thay thế
seanb

7
@BeowulfOF - phá vỡ sẽ chỉ thoát ra khỏi vòng lặp bên trong, không phải các vòng lặp bên trong và bên ngoài.
Greg Beech

36
Bản thân Goto không xấu. Điều gì là xấu xí là lạm dụng goto dẫn đến mã spaghetti. Sử dụng goto để thoát ra khỏi vòng lặp lồng nhau là hoàn hảo ok. Bên cạnh đó, lưu ý rằng tất cả phá vỡ, tiếp tục và trở lại, từ quan điểm lập trình cấu trúc, hầu như không tốt hơn goto - về cơ bản chúng là cùng một thứ, chỉ trong bao bì đẹp hơn. Đó là lý do tại sao các ngôn ngữ cấu trúc thuần túy (như Pascal gốc) thiếu cả ba.
el.pescado

11
@ el.pescado hoàn toàn đúng: nhiều lập trình viên đã sai lầm khi tin rằng gotocó hại cho mọi người , trong khi đó đơn giản là công cụ chính xác để làm một số việc, và giống như nhiều công cụ có thể bị sử dụng sai. Sự ác cảm tôn giáo này chống lại gotothẳng thắn khá ngu ngốc và chắc chắn là không khoa học.
o0 '.

96

Thích ứng C # của cách tiếp cận thường được sử dụng trong C - đặt giá trị của biến vòng lặp ngoài vòng ngoài điều kiện vòng lặp (nghĩa là đối với vòng lặp sử dụng biến int INT_MAX -1 thường là lựa chọn tốt):

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    
}

Như lưu ý trong mã nói rằng breaksẽ không nhảy một cách kỳ diệu đến lần lặp tiếp theo của vòng lặp bên ngoài - vì vậy nếu bạn có mã bên ngoài vòng lặp bên trong thì cách tiếp cận này đòi hỏi nhiều kiểm tra hơn. Xem xét các giải pháp khác trong trường hợp như vậy.

Cách tiếp cận này hoạt động với forwhilecác vòng lặp nhưng không hiệu quả foreach. Trong trường hợpforeach bạn sẽ không có quyền truy cập mã vào điều tra viên ẩn để bạn không thể thay đổi nó (và ngay cả khi bạn IEnumeratorkhông thể có một số phương thức "MoveToEnd").

Lời cảm ơn đến các tác giả bình luận nội tuyến:
i = INT_MAX - 1đề xuất bởi Meta
for / foreachbình luận của ygoe .
Đúng IntMaxbởi jmbpiano
nhận xét về mã sau vòng lặp bên trong bởi blizpasta


@DrG: Không hoạt động trong c # vì "Câu lệnh ngắt kết thúc vòng lặp bao quanh gần nhất hoặc câu lệnh chuyển đổi trong đó nó xuất hiện." (
msd

1
@blizpasta Vậy sao? Nếu anh ta làm cho điều kiện trên vòng lặp bên ngoài là sai (như anh ta đã làm), nó sẽ thoát cả hai.
Patrick

51
bạn nên sử dụng i = INT_MAX - 1; nếu không thì i ++ == INT_MIN <100 và vòng lặp sẽ tiếp tục
Meta

6
bất kỳ ý tưởng về foreach?
ktutnik

4
@ktutnik Nó sẽ không hoạt động với foreach vì bạn sẽ không có quyền truy cập mã vào trình liệt kê ẩn. Ngoài ra IEnumerator không có một số phương thức "MoveToEnd".
ygoe

39

Giải pháp này không áp dụng cho C #

Đối với những người tìm thấy câu hỏi này thông qua các ngôn ngữ khác, Javascript, Java và D cho phép ngắt và tiếp tục được gắn nhãn :

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

40
thật đáng buồn vì điều này không thể được thực hiện với c #, nó sẽ tạo ra mã sạch hơn rất nhiều lần.
Rickard

17
Tôi thực sự trở nên phấn khích trong một giây cho đến khi tôi nhận ra điều này KHÔNG dành cho c #. :(
Arvo Bowen

1
Khái niệm này cũng dành cho PowerShell trong trường hợp ai đó gặp phải vấn đề ở đó. (Họ chỉ đặt dấu hai chấm trước tên nhãn.) Bây giờ tôi biết đây không phải là đặc trưng của PowerShell ... Cú pháp này sẽ không tương thích với các nhãn goto có sẵn trong C #. PHP sử dụng một cái gì đó khác: break 3; Đặt số cấp sau khi tuyên bố phá vỡ.
anh

1
Đây là java không phải c #
Luca Ziegler

Đối với những người muốn xem tính năng này trong C #, hãy đặt kudos của bạn ở đây, vui lòng 😉 github.com/dotnet/csharplang/issues/869#issuecomment-326606003
m93a

28

Sử dụng một bảo vệ phù hợp trong vòng lặp bên ngoài. Đặt bảo vệ trong vòng lặp bên trong trước khi bạn phá vỡ.

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

Hoặc tốt hơn nữa, trừu tượng vòng lặp bên trong thành một phương thức và thoát khỏi vòng lặp bên ngoài khi nó trả về false.

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

    .... some outer loop stuff

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

    ... more outer loop stuff
}

4
Ngoại trừ OP nói "Tôi không muốn phải sử dụng boolean".
LeopardSkinPillBoxHat

Rất Pascal-ish ... Tôi có lẽ nên sử dụng một chiếc goto, mặc dù tôi thường tránh chúng như bệnh dịch.
Jonathan Leffler

21

Đừng trích dẫn tôi về điều này, nhưng bạn có thể sử dụng goto như được đề xuất trong MSDN. Có các giải pháp khác, như bao gồm một cờ được kiểm tra trong mỗi lần lặp của cả hai vòng. Cuối cùng, bạn có thể sử dụng một ngoại lệ như một giải pháp thực sự nặng nề cho vấn đề của mình.

ĐI ĐẾN:

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

Tình trạng:

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

Ngoại lệ:

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
đây là tất cả các cách giải quyết khó khăn, nơi nó sẽ cực kỳ sạch sẽ khi chỉ cần tham gia vào một phương thức và sử dụng trả lại sớm
Dustin Getz

3
:) đúng, đó là một giải pháp đơn giản, nhưng bạn sẽ phải chuyển tất cả dữ liệu cục bộ cần thiết vào phương thức làm đối số ... Đây là một trong số ít nơi goto có thể là giải pháp thích hợp
David Rodríguez - dribeas

Phương thức điều kiện thậm chí không hoạt động vì "nhiều mã" sẽ được thực thi một lần sau khi thoát khỏi vòng lặp bên trong trước khi thoát khỏi vòng lặp bên ngoài. Phương pháp GOTO hoạt động nhưng thực hiện chính xác những gì người đăng cho biết họ không muốn làm. Phương thức Exception hoạt động nhưng xấu hơn và chậm hơn GOTO.
Lập trình viên Windows

Tôi sẽ làm nổi bật dấu chấm phẩy đó sau nhãn. Bằng cách này, nhãn có thể thậm chí ở cuối một khối. +1
Tamas Hegedus

@Windowsprogrammer OP hỏi: "Cách nhanh chóng và hay ho này là gì?" Goto là giải pháp ưa thích: sạch sẽ và súc tích, không liên quan đến một phương pháp riêng biệt.
Suncat2000

16

Có thể cấu trúc lại vòng lặp lồng nhau thành một phương thức riêng không? Bằng cách đó, bạn có thể chỉ cần 'quay trở lại' khỏi phương thức để thoát khỏi vòng lặp.


Với lợi ích phụ là làm cho phương thức ban đầu của bạn ngắn hơn :-)
Martin Capodici

C ++ 11 lambdas làm điều này dễ dàng trong một số trường hợp:[&] { ... return; ... }();
BCS

14

Dường như với tôi như mọi người không thích một gototuyên bố rất nhiều, vì vậy tôi cảm thấy cần phải nói thẳng ra điều này một chút.

Tôi tin rằng mọi người 'cảm xúc' gotocuối cùng đã hiểu rõ về mã và (quan niệm sai lầm) về ý nghĩa hiệu suất có thể có. Do đó, trước khi trả lời câu hỏi, trước tiên tôi sẽ đi vào một số chi tiết về cách nó được biên dịch.

Như chúng ta đã biết, C # được biên dịch sang IL, sau đó được biên dịch thành trình biên dịch bằng trình biên dịch SSA. Tôi sẽ cung cấp một chút thông tin chi tiết về cách thức hoạt động của tất cả, và sau đó cố gắng tự trả lời câu hỏi.

Từ C # đến IL

Đầu tiên chúng ta cần một đoạn mã C #. Hãy bắt đầu đơn giản:

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

Tôi sẽ làm điều này từng bước để cung cấp cho bạn một ý tưởng tốt về những gì xảy ra dưới mui xe.

Bản dịch đầu tiên: từ vòng lặp foreachtương đương for(Lưu ý: Tôi đang sử dụng một mảng ở đây, vì tôi không muốn tìm hiểu chi tiết về IDis Dùng - trong trường hợp đó tôi cũng phải sử dụng IEnumerable):

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

Bản dịch thứ hai: forbreakđược dịch thành tương đương dễ dàng hơn:

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

Và bản dịch thứ ba (đây là tương đương với mã IL): chúng tôi thay đổi breakwhilethành một nhánh:

    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; 

Trong khi trình biên dịch thực hiện những điều này trong một bước duy nhất, nó cung cấp cho bạn cái nhìn sâu sắc về quy trình. Mã IL phát triển từ chương trình C # là bản dịch theo nghĩa đen của mã C # cuối cùng. Bạn có thể tự mình xem tại đây: https://dotnetfiddle.net/QaiLRz (nhấp vào 'xem IL')

Bây giờ, một điều bạn đã quan sát ở đây là trong quá trình, mã trở nên phức tạp hơn. Cách dễ nhất để quan sát điều này là bởi thực tế là chúng ta cần ngày càng nhiều mã để hoàn thành điều tương tự. Bạn cũng có thể cho rằng foreach, for, whilebreakthực sự là ngắn tay cho goto, mà là một phần sự thật.

Từ IL đến Trình biên dịch

Trình biên dịch .NET JIT là trình biên dịch SSA. Tôi sẽ không đi sâu vào tất cả các chi tiết của mẫu SSA ở đây và cách tạo một trình biên dịch tối ưu hóa, nó chỉ là quá nhiều, nhưng có thể cung cấp một sự hiểu biết cơ bản về những gì sẽ xảy ra. Để hiểu sâu hơn, tốt nhất là bắt đầu đọc về tối ưu hóa trình biên dịch (Tôi thích cuốn sách này để giới thiệu ngắn gọn: http://ssabook.gforge.inria.fr/latest/book.pdf ) và LLVM (llvm.org) .

Mọi trình biên dịch tối ưu hóa đều dựa trên thực tế là mã dễ dàng và tuân theo các mẫu có thể dự đoán được . Trong trường hợp các vòng lặp FOR, chúng tôi sử dụng lý thuyết đồ thị để phân tích các nhánh và sau đó tối ưu hóa những thứ như cycli trong các nhánh của chúng tôi (ví dụ như các nhánh ngược).

Tuy nhiên, bây giờ chúng tôi có các chi nhánh chuyển tiếp để thực hiện các vòng lặp của chúng tôi. Như bạn có thể đoán, đây thực sự là một trong những bước đầu tiên mà JIT sẽ sửa chữa, như thế này:

    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:
    // ...

Như bạn có thể thấy, bây giờ chúng ta có một nhánh lạc hậu, đó là vòng lặp nhỏ của chúng ta. Điều duy nhất vẫn còn khó chịu ở đây là chi nhánh mà chúng tôi đã kết thúc do breaktuyên bố của chúng tôi . Trong một số trường hợp, chúng ta có thể di chuyển cái này theo cùng một cách, nhưng trong những trường hợp khác, nó vẫn ở đó.

Vậy tại sao trình biên dịch làm điều này? Chà, nếu chúng ta có thể hủy đăng ký vòng lặp, chúng ta có thể vector hóa nó. Chúng tôi thậm chí có thể chứng minh rằng chỉ có các hằng số được thêm vào, có nghĩa là toàn bộ vòng lặp của chúng tôi có thể tan biến vào không khí mỏng. Tóm lại: bằng cách làm cho các mẫu có thể dự đoán được (bằng cách làm cho các nhánh có thể dự đoán được), chúng ta có thể chứng minh rằng các điều kiện nhất định giữ trong vòng lặp của chúng ta, điều đó có nghĩa là chúng ta có thể làm phép thuật trong quá trình tối ưu hóa JIT.

Tuy nhiên, các nhánh có xu hướng phá vỡ các mô hình dự đoán tốt đẹp này, đó là điều tối ưu hóa do đó không thích - một sự không thích. Phá vỡ, tiếp tục, goto - tất cả họ đều có ý định phá vỡ các mô hình dự đoán này - và do đó không thực sự 'tốt đẹp'.

Tại thời điểm này, bạn cũng nên nhận ra rằng một đơn giản dễ foreachdự đoán hơn sau đó là một loạt các gototuyên bố đi khắp nơi. Xét về (1) khả năng đọc và (2) từ góc độ tối ưu hóa, cả hai đều là giải pháp tốt hơn.

Một điều đáng nói nữa là nó rất phù hợp để tối ưu hóa trình biên dịch để gán các thanh ghi cho các biến (một quá trình gọi là cấp phát thanh ghi ). Như bạn có thể biết, chỉ có một số lượng thanh ghi hữu hạn trong CPU của bạn và chúng là phần bộ nhớ nhanh nhất trong phần cứng của bạn. Các biến được sử dụng trong mã trong vòng lặp bên trong nhất, có nhiều khả năng được đăng ký được gán, trong khi các biến bên ngoài vòng lặp của bạn ít quan trọng hơn (vì mã này có thể bị ảnh hưởng ít hơn).

Giúp đỡ, quá phức tạp ... tôi nên làm gì?

Điểm mấu chốt là bạn phải luôn luôn sử dụng các cấu trúc ngôn ngữ mà bạn có theo ý của bạn, điều này thường sẽ (ngụ ý) xây dựng các mẫu có thể dự đoán được cho trình biên dịch của bạn. Cố gắng tránh các chi nhánh lạ nếu có thể (cụ thể là: break, continue, gotohoặc returnở giữa không có gì).

Tin tốt ở đây là những mẫu có thể dự đoán này vừa dễ đọc (đối với con người) vừa dễ phát hiện (đối với trình biên dịch).

Một trong những mẫu đó được gọi là SESE, viết tắt của Single Entry Single Exit.

Và bây giờ chúng ta đến câu hỏi thực sự.

Hãy tưởng tượng rằng bạn có một cái gì đó như thế này:

// a is a variable.

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

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

Cách dễ nhất để biến điều này thành một mô hình có thể dự đoán được là chỉ cần loại bỏ ifhoàn toàn:

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

Trong các trường hợp khác, bạn cũng có thể chia phương thức thành 2 phương thức:

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

Biến tạm thời? Tốt, xấu hay xấu?

Bạn thậm chí có thể quyết định trả về một boolean từ trong vòng lặp (nhưng cá nhân tôi thích biểu mẫu SESE hơn vì đó là cách trình biên dịch sẽ nhìn thấy nó và tôi nghĩ rằng nó dễ đọc hơn).

Một số người nghĩ rằng việc sử dụng một biến tạm thời sẽ sạch hơn và đề xuất một giải pháp như thế này:

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

Cá nhân tôi phản đối cách tiếp cận này. Nhìn lại về cách mã được biên dịch. Bây giờ hãy nghĩ về những gì nó sẽ làm với những mẫu đẹp, có thể dự đoán được. Lấy tấm hình?

Phải, để tôi đánh vần nó ra. Điều gì sẽ xảy ra là:

  • Trình biên dịch sẽ viết ra mọi thứ như các nhánh.
  • Là một bước tối ưu hóa, trình biên dịch sẽ thực hiện phân tích luồng dữ liệu trong nỗ lực loại bỏ morebiến lạ chỉ xảy ra được sử dụng trong luồng điều khiển.
  • Nếu thành công, biến moresẽ bị loại khỏi chương trình và chỉ còn lại các nhánh. Các nhánh này sẽ được tối ưu hóa, vì vậy bạn sẽ chỉ nhận được một nhánh duy nhất ra khỏi vòng lặp bên trong.
  • Nếu không hiệu quả, biến morechắc chắn được sử dụng trong vòng lặp bên trong nhất, vì vậy nếu trình biên dịch sẽ không tối ưu hóa nó đi, nó có khả năng cao được phân bổ cho một thanh ghi (ăn hết bộ nhớ đăng ký có giá trị).

Vì vậy, để tóm tắt: trình tối ưu hóa trong trình biên dịch của bạn sẽ gặp rất nhiều rắc rối khi chỉ ra rằng morenó chỉ được sử dụng cho luồng điều khiển và trong trường hợp tốt nhất sẽ chuyển nó sang một nhánh duy nhất bên ngoài vòng.

Nói cách khác, trường hợp tốt nhất là nó sẽ kết thúc với tương đương với điều này:

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:

// ...

Ý kiến ​​cá nhân của tôi về điều này khá đơn giản: nếu đây là những gì chúng tôi dự định, hãy làm cho thế giới dễ dàng hơn cho cả trình biên dịch và khả năng đọc, và viết nó ngay lập tức.

tl; dr:

Dòng dưới cùng:

  • Sử dụng một điều kiện đơn giản trong vòng lặp for của bạn nếu có thể. Bám sát các cấu trúc ngôn ngữ cấp cao mà bạn có sẵn theo ý của bạn càng nhiều càng tốt.
  • Nếu mọi thứ đều thất bại và bạn đang trái với một trong hai gotohoặc bool more, thích cũ.

OTOH: Tôi hiếm khi chỉ muốn phá vỡ các vòng lặp bên ngoài hoặc mong muốn goto-Equiv (và rất nhiều mã có thể được viết sạch hơn), mặc dù điều đó có thể phổ biến hơn trong các lĩnh vực khác .. ngày nay là trường hợp như vậy, nhưng điều đó phần lớn là do yield return. Đó là, trong khi thật dễ dàng để thấy có một số trường hợp hữu ích, đối với hầu hết mã "cấp độ ứng dụng", có lẽ sự thất vọng rất thấp với C #, ngoại trừ những trường hợp "chỉ đến từ" C / C ++ ;-) Tôi cũng thỉnh thoảng bỏ lỡ Đôi khi "ném" của Ruby (không ngoại lệ) vì nó cũng phù hợp với miền này.
dùng2864740

1
@ Suncat2000 Chỉ cần giới thiệu nhiều mã hơn như các biến và các nhánh có điều kiện để tránh sử dụng a goto, không làm cho mã của bạn dễ đọc hơn - Tôi tranh luận điều gì xảy ra hoàn toàn ngược lại: thực tế, luồng điều khiển của bạn sẽ chứa nhiều nút hơn - khá nhiều định nghĩa về độ phức tạp. Chỉ cần tránh nó để hỗ trợ kỷ luật tự giác có vẻ phản tác dụng về khả năng đọc.

@atlaste: Xin lỗi, hãy để tôi nêu nhận xét của tôi rõ ràng hơn. Điều tôi nên nói là: Giải thích tốt đẹp. Nhưng mọi người tránh goto không phải là về hiệu suất; đó là về việc tránh lạm dụng gây ra sự thiếu khả năng đọc. Chúng ta có thể cho rằng những người tránh gotochỉ không có kỷ luật để không lạm dụng nó.
Suncat2000

@atlaste, ngôn ngữ nào là "ackomplish"?
Pacerier

11

đưa vào một hàm / phương thức và sử dụng trả về sớm hoặc sắp xếp lại các vòng lặp của bạn thành mệnh đề while. goto / ngoại lệ / bất cứ điều gì chắc chắn là không thích hợp ở đây.

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

10

Bạn đã yêu cầu một sự kết hợp nhanh chóng, tốt đẹp, không sử dụng boolean, không sử dụng goto và C #. Bạn đã loại trừ tất cả các cách có thể để làm những gì bạn muốn.

Cách nhanh nhất và ít xấu xí nhất là sử dụng goto.


Hoàn toàn đồng ý. Giới thiệu một phương pháp mới chỉ để loại bỏ một goto duy nhất là ngớ ngẩn. Nếu trình biên dịch không thể thực hiện cuộc gọi phương thức đó vì bất kỳ lý do gì - chúng tôi sẽ kết thúc với chi phí hoàn toàn không cần thiết của một cuộc gọi phương thức bổ sung. Ném và bắt ngoại lệ chỉ để phá vỡ vòng lặp vừa dài vừa đắt hơn một cách lố bịch.
Zar Shardan

@Windowsprogramm: OP không yêu cầu "không sử dụng goto". Anh không muốn "đi đến một phương pháp khác". Câu hỏi không thể loại trừ tất cả các cách có thể, nhưng bạn có quyền ám chỉ rằng goto là tốt nhất ở đây.
Suncat2000

5

Đôi khi rất hay để trừu tượng mã thành chức năng của chính nó và hơn là sử dụng trả lại sớm - trả về sớm là xấu :)

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
nhưng sau đó, điều này cho phép giải pháp truyền thống được OP
Surya Pratap

2

Tôi đã thấy rất nhiều ví dụ sử dụng "break" nhưng không có ví dụ nào sử dụng "continue".

Nó vẫn sẽ yêu cầu một cờ nào đó trong vòng lặp bên trong:

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

Kể từ lần đầu tiên tôi nhìn thấy breakở C vài thập kỷ trước, vấn đề này đã làm tôi bực mình. Tôi đã hy vọng một số cải tiến ngôn ngữ sẽ có một phần mở rộng để phá vỡ, do đó sẽ hoạt động:

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
Sau đó, một lập trình viên bảo trì sẽ chèn một mức lồng nhau khác, sẽ sửa một số câu lệnh break và sẽ phá vỡ một số câu lệnh break khác. Thay vào đó, cách khắc phục là phá vỡ nhãn. Điều đó đã thực sự được đề xuất, nhưng những người thực dụng sử dụng goto một nhãn thay thế.
Lập trình viên Windows

Chờ đã, ai bảo trì lập trình nữa? :)
Jesse C. Choper

2
JavaScript thậm chí đã dán nhãn khối / tuyên bố phá vỡ. devguru.com/Technology/ecmascript/quickref/break.html
David Grant

@Chris Bartow: tuyệt! đã làm cho Giáng sinh của tôi :) @David Grant: vì vậy có vẻ như JS phá vỡ == Coto?
Jesse C. Máy cắt lát

D đã dán nhãn break / continue
BCS

0

Tôi nhớ từ thời còn là sinh viên, người ta nói rằng về mặt toán học có thể chứng minh rằng bạn có thể làm bất cứ điều gì trong mã mà không cần goto (tức là không có tình huống nào goto là câu trả lời duy nhất). Vì vậy, tôi không bao giờ sử dụng goto (chỉ là sở thích cá nhân của tôi, không gợi ý rằng tôi đúng hay sai)

Dù sao, để thoát ra khỏi các vòng lặp lồng nhau, tôi làm một cái gì đó như thế này:

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

... Tôi hy vọng điều đó có ích cho những người như tôi là những "fanboys" chống goto :)


Xin lỗi để nói với bạn, nhưng giảng viên của bạn là người đổ lỗi cho tình trạng của bạn. Nếu anh ấy bận tâm ép bạn học lắp ráp, thì bạn biết rằng 'goto' chỉ là một bước nhảy (tất nhiên tôi bỏ qua thực tế rằng đây là câu hỏi ac #).
cmroanirgo

0

Đó là cách tôi đã làm nó. Vẫn là một cách giải quyết.

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

Cách dễ nhất để kết thúc một vòng lặp kép sẽ trực tiếp kết thúc vòng lặp đầu tiên

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

Điều này không hoạt động như bằng cách sử dụng break bạn sẽ kết thúc vòng lặp bên trong. Vòng lặp bên ngoài sẽ tiếp tục lặp lại.
Alex Leo

0

Vòng lặp có thể bị phá vỡ bằng cách sử dụng các điều kiện tùy chỉnh trong vòng lặp, cho phép có mã sạch.

    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;   

Với mã này, chúng tôi giữ nguyên đầu ra sau:

  • Lặp lại vòng lặp bên ngoài 0
  • Lặp lại vòng lặp bên trong 0
  • Lặp lại vòng lặp bên trong 1
  • Lặp lại vòng lặp bên trong 2
  • Lặp lại vòng lặp bên trong 3
  • Lặp lại vòng lặp bên trong 4
  • Mã sau vòng lặp bên trong sẽ được thực thi khi ngắt

0

Một tùy chọn khác là một chức năng ẩn danh tự gọi . Không có goto, không nhãn, không có biến mới, không có tên hàm mới được sử dụng và ngắn hơn một dòng so với ví dụ về phương thức ẩn danh ở trên cùng.

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

Ném một ngoại lệ tùy chỉnh đi ra ngoài vòng lặp.

Nó hoạt động cho for, foreachhoặc whilebất kỳ loại vòng lặp và bất kỳ ngôn ngữ nào sử dụng try catch exceptionkhối

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

Sự khác biệt cần thiết với câu trả lời của dviljoen là gì?
Gert Arnold

Điều đó sẽ không hoạt động, vì bạn không kiểm tra điều kiện "breakInnerLoop" ở vòng lặp ngoài, vì vậy bạn chỉ cần lặp lại vòng lặp tiếp theo, bạn cũng đã viết j và J, và bỏ qua dấu chấm phẩy, sẽ không biên dịch.
Sebastian

-4

Như tôi thấy bạn đã chấp nhận câu trả lời trong đó người đề cập đến bạn tuyên bố goto, trong đó trong lập trình hiện đại và theo ý kiến ​​chuyên gia, goto là một kẻ giết người, chúng tôi gọi đó là một kẻ giết người trong lập trình có một số lý do nhất định, mà tôi sẽ không thảo luận về nó ở đây tại thời điểm này, nhưng giải pháp cho câu hỏi của bạn rất đơn giản, bạn có thể sử dụng cờ Boolean trong loại kịch bản này giống như tôi sẽ trình bày nó trong ví dụ của mình:

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

đơn giản và đơn giản. :)


đọc câu hỏi trước khi đề nghị nghỉ Do đó các downvote.
cmroanirgo

1
đọc nó ngay bây giờ, nhưng khi tôi đăng câu trả lời này, phiên bản chỉnh sửa không sử dụng boolean đã không được thêm vào, đây là lý do tại sao tôi đăng câu trả lời này .. nhưng cảm ơn vì đã tải xuống!
Steve

1
Đó không phải là một việc cá nhân. Thật không may, phiếu bầu hiện đã bị khóa;) Đó là một phần cần thiết của SO để có được câu trả lời tốt nhất cho đầu trang (điều này không phải lúc nào cũng xảy ra)
cmroanirgo

-6

Bạn thậm chí đã nhìn vào breaktừ khóa? Ôi

Đây chỉ là mã giả, nhưng bạn sẽ có thể thấy những gì tôi muốn nói:

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

Nếu bạn nghĩ về việc breaklà một chức năng nhưbreak() , thì tham số của nó sẽ là số vòng lặp để thoát ra. Khi chúng ta ở trong vòng lặp thứ ba trong mã ở đây, chúng ta có thể thoát ra khỏi cả ba.

Hướng dẫn sử dụng: http://php.net/break


13
Không phải là một câu hỏi php.
Zerga

-10

Tôi nghĩ trừ khi bạn muốn làm "điều boolean", giải pháp duy nhất thực sự là ném. Mà rõ ràng là bạn không nên làm ..!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.