Vòng lặp while
Vòng lặp while trong R tiếp tục chạy thân vòng lặp chừng nào điều kiện còn là TRUE. Trong khi vòng lặp for chạy một lần cho mỗi phần tử của một thứ gì đó, while chạy một số lần không biết trước, cho đến khi điều kiện thay đổi:
Ba phần làm nên mọi vòng lặp while, và cả ba đều thấy rõ ở đây:
- Khởi tạo trước vòng lặp:
count <- 1cho điều kiện một thứ để kiểm tra. - Điều kiện:
count <= 5, tạo từ các toán tử so sánh quen thuộc, được kiểm tra trước mỗi lượt. - Cập nhật bên trong thân:
count <- count + 1đưa vòng lặp tiến dần đến điểm dừng.
Xóa phần 3 thì điều kiện mãi mãi là TRUE: vòng lặp không bao giờ kết thúc. Đó là rủi ro đặc trưng của while, và phần lớn nội dung còn lại của trang này nói về cách kiểm soát nó.
Điều kiện được kiểm tra trước mỗi lượt
while kiểm tra điều kiện trước, rồi mới chạy thân. Có hai hệ quả. Nếu điều kiện đã là FALSE ngay từ đầu, thân vòng lặp chạy 0 lần:
Và việc kiểm tra diễn ra giữa các lượt trọn vẹn, không phải giữa chừng thân vòng lặp: một khi lượt đã bắt đầu, nó chạy đến hết dù điều kiện thành FALSE ở giữa. Nếu bạn cần thoát ra giữa chừng, đó là việc của break (bên dưới).
Ví dụ: chia đôi đến khi nhỏ hơn ngưỡng
Chỗ đứng tự nhiên của while là "cứ tiếp tục cho đến khi": bạn không thể biết trước cần bao nhiêu lượt, nên for không phù hợp. Bạn có thể chia đôi 1000 bao nhiêu lần trước khi nó nhỏ hơn 1?
Mười lần chia đôi, kết thúc ở khoảng 0,977. Mẫu này (một biến trạng thái mà thân vòng lặp làm nhỏ dần, một biến đếm mà nó tăng dần) bao quát một phần rất lớn các vòng lặp while thực tế: phương pháp số chạy cho đến khi sai số đủ nhỏ, mô phỏng chạy cho đến khi cạn tài nguyên, thử lại cho đến khi lệnh gọi thành công.
repeat + break: do-while của R
R không có từ khóa do-while, tức không có vòng lặp nào kiểm tra điều kiện sau thân. Nhưng R có repeat, lặp vô điều kiện mãi mãi, và break, thoát khỏi bất kỳ vòng lặp nào ngay lập tức. Đặt phép kiểm tra break ở cuối thân repeat là bạn đã dựng đúng một vòng do-while, vòng lặp có thân chắc chắn chạy ít nhất một lần:
Thân vòng lặp luôn chạy trước lần kiểm tra đầu tiên, nên n được nhân đôi ít nhất một lần; vòng lặp thoát ở 128, lần nhân đôi đầu tiên vượt quá 100. Dùng dạng này khi "làm việc đó, rồi quyết định có làm tiếp không" là mô tả đúng nhất: đọc đầu vào cho đến khi hợp lệ, lấy mẫu cho đến khi có một mẫu đạt yêu cầu. Một repeat không có break nào chạm tới được thì về bản chất là vòng lặp vô hạn, nên hãy viết break trước, rồi mới điền phần thân.
break hoạt động giống hệt trong vòng lặp while và for: đó là lối thoát chung "xong việc ở đây rồi", và đi rất hợp với câu lệnh if kiểm tra một điều kiện giữa thân vòng lặp.
next bên trong vòng lặp while
next bỏ qua phần còn lại của lượt hiện tại và quay lại bước kiểm tra điều kiện. Nó hoạt động trong while y như trong for, nhưng có một cái bẫy riêng của while: nếu bước cập nhật nằm sau next, việc bỏ qua nó khiến biến lặp không bao giờ tăng, và bạn đã tạo ra một vòng lặp vô hạn. Hãy đặt bước cập nhật trước next:
Đoạn code này in ra các số chẵn từ 2 đến 10. Chuyển i <- i + 1 xuống dưới dòng next thì giá trị lẻ đầu tiên sẽ bỏ qua bước cập nhật mãi mãi. Trong vòng lặp for không có nguy cơ này (biến lặp tự động tăng), thêm một lý do để ưu tiên for khi bạn chỉ đơn giản là duyệt qua một dãy.
Phòng tránh vòng lặp vô hạn
Mọi vòng lặp vô hạn đều là cùng một bug: không gì trong thân vòng lặp thay đổi các biến mà điều kiện phụ thuộc vào, hoặc thay đổi chúng theo hướng sai. Khi làm việc tương tác, bạn có thể ngắt bằng Esc hoặc Ctrl+C, nhưng trong một script thì tiến trình cứ thế treo. Hai thói quen giúp phòng tránh.
Thứ nhất, khi viết điều kiện, hãy hỏi ngay "dòng nào trong thân đưa điều kiện này về FALSE?" và bảo đảm dòng đó chạy trên mọi nhánh đi qua thân vòng lặp (cẩn thận cái bẫy next ở trên).
Thứ hai, với những vòng lặp mà bạn khó chứng minh là sẽ kết thúc, hãy thêm giới hạn số lần lặp để một bug logic cho ra kết quả có thể chẩn đoán thay vì làm treo phiên làm việc. Ở đây nó bảo vệ một vòng lặp kiểu Collatz: liên tục chia đôi số chẵn và biến x lẻ thành 3 * x + 1, cho đến khi đạt 1:
Bắt đầu từ 10, vòng lặp đạt 1 sau 6 bước. Nếu có giá trị khởi đầu nào không bao giờ đạt 1, mệnh đề steps < 10000 vẫn kết thúc vòng lặp, và trạng thái được in ra sẽ cho bạn biết có gì đó không ổn: một câu trả lời sai mà bạn nhìn thấy được còn hơn một chương trình không bao giờ trả lời. Toán tử ngắn mạch && là lựa chọn đúng ở đây: hai vế đều là giá trị đơn, và điều kiện đọc lên như một câu trọn vẹn.
while hay for: chọn thế nào
Quy tắc kinh nghiệm dựa trên điều bạn biết trước:
- Bạn biết mình đang duyệt qua cái gì (các phần tử của vector, các số từ 1 đến n): dùng vòng lặp for, biến lặp tự quản lý.
- Bạn chỉ biết điều kiện dừng (đến khi hội tụ, đến khi nhỏ hơn ngưỡng, đến khi hợp lệ): dùng
while. - Thân vòng lặp phải chạy ít nhất một lần trước khi phép kiểm tra nào có nghĩa: dùng
repeatvới mộtbreakđể kết thúc.
Nếu bạn thấy mình đang tự quản lý một biến đếm trong vòng lặp while chỉ để duyệt một vector, đó là một vòng lặp for đang đội lốt: hãy chuyển sang for.
Điều cần nhớ
while (condition) { ... }kiểm tra trước, rồi mới chạy: điều kiện sai ngay từ đầu nghĩa là 0 lượt.- Thân vòng lặp phải đưa điều kiện về phía
FALSE; bộ ba khởi tạo, điều kiện, cập nhật là toàn bộ nguyên tắc. - R không có từ khóa do-while:
repeat { body; if (done) break }là cách viết quen thuộc, bảo đảm ít nhất một lượt. breakthoát khỏi mọi vòng lặp; khi dùngnexttrongwhile, hãy cập nhật biến lặp trước khi bỏ qua, nếu không nó sẽ không bao giờ tăng.- Giới hạn số lần lặp (
&& steps < 10000) biến một lần treo tiềm ẩn thành một câu trả lời sai nhìn thấy được và gỡ lỗi được.
Tiếp theo: vector, cấu trúc dữ liệu mà mọi vòng lặp trong trang này đã duyệt qua, và là lý do nhiều vòng lặp R có thể biến mất hoàn toàn.
Câu hỏi thường gặp
Vòng lặp while hoạt động thế nào trong R?
while (condition) { ... } kiểm tra điều kiện trước, và chỉ chạy thân vòng lặp khi điều kiện còn là TRUE. Phải có thứ gì đó trong thân vòng lặp cuối cùng làm điều kiện thành FALSE, thường là cập nhật một biến đếm hoặc giảm một giá trị, nếu không vòng lặp sẽ không bao giờ kết thúc. Nếu điều kiện là FALSE ngay từ đầu, thân vòng lặp chạy 0 lần.
R có vòng lặp do-while không?
Không có từ khóa này. Cách viết quen thuộc là repeat { ...body...; if (condition) break }: repeat lặp vô điều kiện, nên thân vòng lặp chắc chắn chạy ít nhất một lần, và break ở cuối đóng vai trò phép kiểm tra thoát của do-while.
Làm sao để dừng vòng lặp vô hạn trong R?
Khi đang làm việc tương tác, nhấn Esc (RStudio) hoặc Ctrl+C (terminal) để ngắt. Để phòng ngay từ đầu: bảo đảm thân vòng lặp cập nhật các biến trong điều kiện ở mỗi lượt, và thêm giới hạn số lần lặp, while (working && steps < 10000), để một bug logic cho ra con số sai thay vì làm treo cả phiên làm việc.