Chương 3. Vòng lặp

Trở về Mục lục cuốn sách

1. Cập nhật các biến

Ở Bài tập cuối Chương 2, bạn có thể đã định viết

a = a - 0.05*a + 0.03*b
b = b + 0.05*a - 0.03*b

Nhưng điều đó sai, rất sai. Tại sao? Vấn đề là ở chỗ dòng lệnh thứ nhất thay đổi giá trị của a, nên khi dòng lệnh thứ hai được thực hiện, nó sẽ lấy giá trị cũ của b và giá trị mới của a. Kết quả là, sự thay đổi ở a không cùng lúc với thay đổi ở b; tức là đã vi phạm định luật bảo toàn số lượng xe!

Một cách làm là dùng các biến tạm thời, anewbnew:

anew = a - 0.05*a + 0.03*b
bnew = b + 0.05*a - 0.03*b
a = anew
b = bnew

Cách này có tác dụng cập nhật các biến “đồng thời”; nghĩa là nó đọc cả hai biến cũ trước khi ghi ra hai giá trị mới.

Dưới đây là một cách làm khác có lợi là làm việc tính toán được đơn giản:

atob = 0.05*a - 0.03*b
a = a - atob
b = b + atob

Xem xét đoạn mã này ta có thể thấy được nó tuân theo định luật bảo toàn số xe. Ngay cả khi giá trị của atob là sai thì ít nhất tổng số xe vẫn còn đúng. Và từ đó dẫn đến Định luật thứ bẩy về gỡ lỗi:

Cách tốt nhất để tránh một lỗi là khiến nó không thể xảy ra.

Trong trường hợp này, việc bỏ những chi tiết thừa cũng loại trừ khả năng gây lỗi.

2. Các loại lỗi

Có bốn loại lỗi sau:

Lỗi cú pháp:
Bạn đã viết một câu lệnh MATLAB mà không thể thực thi được vì nó vi phạm các quy tắc về cú pháp. Chẳng hạn, bạn không thể có hai toán hạng đi liền nhau mà không có toán tử, vì vậy pi r^2 là một lỗi cú pháp. Khi MATLAB phát hiện ra lỗi cú pháp, nó sẽ hiển thị một thông báo lỗi và dừng chương trình.

Lỗi thực thi:
Chương trình của bạn đã bắt đầu chạy, nhưng rồi có điều gì trục trặc diễn ra. Chẳng hạn, nếu bạn cố gắng truy cập một biến chưa tồn tại thì đó là một lỗi thực thi. Khi MATLAB phát hiện được vấn đề, nó sẽ in ra thông báo lỗi và dừng lại.

Lỗi logic:
Chương trình chạy mà không phát sinh bất cứ thông báo lỗi nào, nhưng nó không thực hiện điều mong muốn. Vấn đề ta gặp ở mục trước, khi thay đổi giá trị của a trước lúc đọc giá trị cũ, là một lỗi logic.

Lỗi số trị:
Hầu hết những phép tính được thực hiện bởi MATLAB đều chỉ gần đúng. Trong đa số trường hợp, sai số là nhỏ và ta không quan tâm đến, nhưng đôi khi các sai số do làm tròn lại là một vấn đề.

Các lỗi cú pháp luôn dễ xử lý nhất. Đôi khi dòng thông báo lỗi có thể gây nhầm lẫn, nhưng thường MATLAB sẽ báo cho bạn biết lỗi ở đâu, ít ra là một vị trí gần đúng.

Các lỗi thực thi nói chung là khó hơn vì như tôi đã đề cập ở trên, MATLAB nói được vị trí của nó nhưng không nói nguyên nhân gây ra nó.

Các lỗi logic đều khó vì MATLAB chẳng giúp gì được. Chỉ có bạn mới biết được chương trình cần phải làm gì, vì vậy chỉ có bạn mới sửa được lỗi. Theo quan điểm của MATLAB, nếu chương trình không có gì sai thì lỗi nằm trong đầu bạn!

Các lỗi số trị có thể sẽ rất mẹo mực vì thật không rõ là cái sai có thuộc về bạn hay không. Với những tính toán đơn giản nhất, MATLAB cho ta các giá trị số có dấu phẩy động gần sát với giá trị đúng, có nghĩa là 15 chữ số ban đầu là đúng. Nhưng trong một số bài toán với đặc thù “tình trạng xấu”, có nghĩa là ngay cả khi chương trình của bạn đã đúng, các sai số do làm tròn vẫn tích tụ lại và số chữ số đúng sẽ ít đi. Đôi khi MATLAB có thể cảnh báo cho bạn biết điều này đang xảy ra, nhưng không phải luôn luôn như vậy! Độ chuẩn xác (số chữ số trong kết quả) không bao hàm độ chính xác (số chữ số đúng).

3. Sai số tuyệt đối và tương đối

Có hai cách nghĩ về các sai số về số trị, đó là tuyệt đốitương đối.

Sai số tuyệt đối chính là độ chênh lệch giữa giá trị đúng và giá trị xấp xỉ. Ta thường biểu thị độ lớn của sai số này, mà bỏ qua dấu của nó, vì dù giá trị xấp xỉ có cao hay thấp thì cũng chẳng ảnh hưởng gì.

Chẳng hạn, ta muốn tính 9! bằng công thức √(18π) ⋅ (9/e)9. Đáp số đúng là 9 ⋅ 8 ⋅ 7 ⋅ 6 ⋅ 5 ⋅ 4 ⋅ 3 ⋅ 2 ⋅ 1 = 362880. Giá trị xấp xỉ tính được là 359536,87. Sai số tuyệt đối là 3343,13.

Thoạt nhìn, ta tưởng chừng như đây là sai số lớn—ta đã tính sai đến hơn 3000—nhưng cũng cần phải xét đến độ lớn của đại lượng mà ta cần tính. Chẳng hạn, $3000 là con số lớn khi nói về tiền lương năm của tôi, nhưng chẳng là gì cả nếu ta nói về khoản nợ của quốc gia.

Một cách tự nhiên để xử lý ván đề này là dùng sai số tương đối, vốn là tỉ lệ (hay số phần trăm) của sai số tuyệt đối so với giá trị đúng. Trong trường hợp này, ta cần chia sai số cho 362880, thu được 0,00921, tức là gần 1%. Với phần lớn các mục đích tính toán khác nhau, thì sai lệch 1% là đạt yêu cầu.

4. Vòng lặp for

Vòng lặp là một phần chương trình được thực hiện lặp đi lặp lại. Vòng lặp for là dạng vòng lặp có dùng câu lệnh for.

Cách dùng vòng lặp for đơn giản nhất laf thực hiện lặp lại một hay nhiều câu lệnh với số lần định trước. Chẳng hạn, ở chương trước ta đã viết một đoạn mã có tên car_update để mô phỏng diễn biến chạy trong một tuần của những xe thuộc một công ty. Để mô phỏng diễn biến trong một năm, ta cần phải chạy nó 52 lần:

for i=1:52
    car_update
end

Dòng thứ nhất giống như một lệnh gán, và nó đúng là một kiểu lệnh gán, nhưng được thực hiện nhiều lần. Lần đầu tiên được chạy, nó tạo ra biến i và gán cho nó giá trị bằng 1. Lần thứ hai, i nhận giá trị 2, và cứ như vậy cho đến 52.

Toán tử hai chấm, :, biểu diễn một khoảng số nguyên. Theo tinh thần của cách kiểm tra từng phần, bạn có thể tạo ra một dãy từ dấu nhắc lệnh:

>> 1:5
ans =  1     2     3     4     5

Biến được dùng trong lệnh for này được gọi là biến vòng lặp. Theo thông lệ, ta thường lấy các tên i, jk để đặt cho các biến lặp.

Những câu lệnh bên trong vòng lặp được gọi là phần thân. Theo thông lệ, chúng được viết thụt đầu dòng để cho thấy rằng chúng nằm trong vòng lặp, tuy nhiên hình thức viết này không ảnh hưởng đến việc thực hiện chương trình. Điểm kết thúc của vòng lặp được chính thức đánh dấu bởi lệnh end.

Để xem vòng lặp hoạt động thế nào, bạn có thể chạy vòng lặp trong đó chỉ in ra biến lặp:

>> for i=1:5
    i
end

i = 1
i = 2
i = 3
i = 4
i = 5

Như ví dụ trên cho thấy, bạn có thể chạy một vòng lặp từ dấu nhắc lệnh, nhưng ta thường đặt nó vào một tập tin lệnh hơn.

Hãy tạo ra một tập tin lệnh có tên car_loop trong đó dùng một vong lặp for để chạy car_update 52 lần. Hãy nhớ rằng trước khi chạy car_update, bạn phải gán các giá trị cho ab. Với bài tập này, hãy bắt đầu bằng các giá trị a = 150b = 150.

Nếu mọi việc trôi chảy, chương trình của bạn sẽ hiển thị một đoạn dài các con số trên màn hình. Nhưng thường có quá nhiều số để màn hình hiện ra hết; và ngay cả có hiện hết đi nữa cũng rất khó diễn giải được. Có một đồ thị sẽ tốt hơn!

5. Đồ thị

plot là một hàm vẽ đồ thị rất đa năng, giúp ta vẽ các điểm, các đường trên hệ tọa độ hai chiều. Thật không may, vì quá đa năng nên nó có thể trở nên khó dùng. (và khó tra cứu thông tin về hàm này!) Ta sẽ bắt đầu một cách đơn giản và dần làm khó hơn.

Để chấm một điểm, ta gõ vào

>> plot(1, 2)

Một Figure Window (cửa sổ hình vẽ) sẽ xuất hiện với một đồ thị trên đó có chấm một điểm màu xanh lam tại tọa độ x bằng 1 và y bằng 2. Để khiến cho điểm này dễ nhìn hơn, bạn có thể chọn một hình khác:

>> plot(1, 2, 'o')

Chữ cái ở trong cặp dấu nháy đơn là một chuỗi chỉ định hình thức của điểm cần chấm. Bạn cũng có thể chỉ định màu sắc:

>> plot(1, 2, 'ro')

r viết tắt cho red (đỏ); các màu khác gồm có green (lục), blue (lam), cyan (da trời), magenta (tím hồng), yellow (vàng) và black (đen). Các hình khác gồm có +, *, x, s (square / hình vuông), d (diamond / hình thoi), và ^ (hình tam giác).

Khi bạn dùng hàm plot theo cách này, mỗi lúc nó chỉ vẽ được một điểm. Nếu bạn chạy plot lần nữa, nó xóa toàn bộ hình vẽ trước khi vẽ hình mới. Lệnh hold giúp bạn thay đổi đặc tính nêu trên. hold on báo với MATLAB rằng không được xóa hình cũ khi vẽ hình mới; và hold off trở lại đặc tính ban đầu.

Hãy thử các lệnh này:

>> hold on

>> plot(1, 1, 'o')
>> plot(2, 2, 'o')

Bạn sẽ nhìn thấy một hình có hai điểm. MATLAB co giãn tỉ lệ đồ thị một cách tự động sao cho các trục chạy từ giá trị nhỏ nhất trên đồ thị đến giá trị lớn nhất. Vì vậy ở ví dụ này, các điểm chấm xuất hiện ở hai góc.

Hãy sửa lại car_loop sao cho qua mỗi vòng lặp, chương trình sẽ chấm lên đồ thị giá trị của a theo i.

Một khi chương trình của bạn chạy được, hãy sửa lại để nó chấm các giá trị của a bằng vòng tròn đỏ và của b bằng hình thoi xanh lam.

Thêm nữa: nếu bạn dùng hold on để ngăn không cho MATLAB xóa hình vẽ, bạn vẫn có thể tự xóa hình theo ý muốn bằng lệnh clf.

6. Dãy

Trong toán học, một dãy là một tập hợp các số tương ứng với các số nguyên dương. Các số trong dãy được gọi là phần tử. Theo kí hiệu toán học, các phần tử được kèm theo các chỉ số dưới, vì vậy phần tử đầu tiên của dãy AA1, tiếp theo là A2, và cứ như vậy.

Vòng lặp for là một cách tự nhiên để tính các phần tử trong một dãy. Chẳng hạn, trong dãy hình học, mỗi phần tử là một bội số (với hệ số không đổi) của số liền trước. Cụ thể, hãy xét dãy số với A= 1 và tỉ lệ Ai+1/Ai = 2, với mọi i. Nói cách khác, mỗi phần tử chỉ lớn bằng nửa phần tử liền trước nó.

Vòng lặp sau đây tính ra 10 phần tử đầu của A:

a = 1
for i=2:10
    a = a/2
end

Mỗi lượt lặp, ta tìm được phần giá trị tiếp theo của a bằng cách chia giá trị trước cho 2. Lưu ý rằng dãy chỉ số bắt đầu từ 2 vì giá trị đầu của a tương ứng với A1, vì vậy lượt lặp đầu tiên ta đi tính A2.

Mỗi lần qua vòng lặp, ta thay thế phần tử trước bởi phần tử kế tiếp, vì vậy về cuối, a chứa phần tử thứ 10. Các phần tử khác được hiển thị trên màn hình, nhưng chúng không được lưu lại trong một biến nào. Sau này, ta sẽ xem cách lưu toàn bộ các phần tử của dãy vào một véc-tơ.

Vòng lặp này tính dãy theo cách truy hồi, nghĩa là mỗi phần tử đều phụ thuộc vào phần tử liền trước nó. Với dạng dãy này ta cũng có thể tính trực tiếp phần tử thứ i, theo một hàm của i, mà không cần dùng đến phần tử đứng trước. Theo cách viết toán học, Ai A1ri–1.

Hãy viết một tập tin lệnh có tên sequence trong đó dùng vòng lặp để tính các phần tử của A một cách trực tiếp.

7. Chuỗi

Trong toán học, chuỗi là tổng các phần tử của một dãy. Cách đặt tên này không hay trong tiếng Anh (“sequence” và “series” gần như có chung nghĩa); nhưng trong toán thì dãy là một tập hợp số, còn chuỗi lại là một biểu thức (một tổng) với một giá trị duy nhất. Theo kí hiệu toán học, một chuỗi thường được viết với dấu tổng .

Chẳng hạn, tổng của 10 phần tử đầu tiên của A

i=110Ai

Một vòng lặp for là cách tự nhiên để tính giá trị của chuỗi này:

A1 = 1;
total = 0;
for i=1:10
    a = A1 * 0.5^(i-1);
    total = total + a;
end
ans = total

A1 là phần tử đầu tiên của dãy, vì vậy ở mỗi lượt lặp, a là phần tử thứ i.

Biến total, theo cách dùng ở đây, đôi khi được gọi là biến tích lũy; tức là một biến mà dồn lại lần lượt từng kết quả của các phép tính. Trước vòng lặp ta đặt biến này bằng 0. Mỗi lượt lặp ta cộng nó với phần tử thứ i. Ở cuối vòng lặp, total sẽ chứa tổng của các phần tử. Vì đó là giá trị mà ta cần tìm, ta gán nó cho ans.

Ví dụ trên đã tính các phần tử của chuỗi một cách trực tiếp; bạn hãy thử viết một tập tin lệnh tên là series để tính cùng tổng đó nhưng với từng phần tử được tính theo cách truy hồi. Bạn sẽ phải cẩn thận về các vị trí bắt đầu và kết thúc vòng lặp.

8. Khái quát hóa

Như đã nói, ví dụ trên luôn luôn lấy tổng 10 phần tử đầu tiên của dãy, nhưng ta có thể tò mò muốn biết giá trị total sẽ như thế nào khi ta tăng số lượng các số hạng có trong chuỗi. Nếu bạn đã biết về chuỗi hình học, bạn thấy rằng chuỗi này hội tụ về 2; nghĩa là khi số các số hạng tiến đến vô cùng, thì tổng sẽ tiệm cận về 2.

Để thấy được liệu điều đó có đúng không, trong chương trình ta sẽ thay thế hằng số, 10, với một biến có tên n:

A1 = 1;
total = 0;
for i=1:n
    a = A1 * 0.5^(i-1);
    total = total + a;
end
ans = total

Bây giờ đoạn chương trình có thể tính với bao nhiêu số hạng cũng được, với điều kiện đầu là bạn phải đặt n trước khi chạy chương trình này. Sau đây là cách chạy chương trình với các giá trị n khác nhau:

>> n=10; series

total = 1.99804687500000

>> n=20; series

total = 1.99999809265137

>> n=30; series

total = 1.99999999813735

>> n=40; series

total = 1.99999999999818

Rõ ràng là nó đang hội tụ về 2.

Việc thay thế một hằng số bởi một biến được gọi là khái quát hóa. Thay vì tính với một số lượng cụ thể và cố định các số hạng, chương trình mới này tổng quát hơn; nó có thể tính với số các số hạng bất kì.

Đây là một ý tưởng quan trọng mà ta sẽ quay về khi thảo luận đến các hàm.

9. Thuật ngữ

sai số tuyệt đối:
Độ chênh lệch giữa giá trị xấp xỉ và kết quả đúng.

sai số tương đối:
Độ chênh lệch giữa giá trị xấp xỉ và kết quả đúng, biểu diễn dưới dạng một phần hoặc số phần trăm của giá trị đúng.

vòng lặp:
Phần của chương trình được chạy đi chạy lại nhiều lần.

biến lặp:
Biến được định nghĩa trong một câu lệnh for; nó được gán các giá trị khác nhau qua mỗi lượt lặp.

khoảng:
Tập hợp các giá trị được gán cho một biến lặp, thường được biểu thị bởi toán tử hai chấm, chẳng hạn 1:5.

phần thân:
Những câu lệnh bên trong vòng lặp được thực hiện lặp lại nhiều lần.

dãy:
Trong toán học, một tật hợp các số tương ứng với những số nguyên.

phần tử:
Một thành viên của tập hợp các số trong dãy.

truy hồi:
Cách tính phần tử kế tiếp trong dãy dựa trên những phần tử liền trước.

trực tiếp:
Cách tính phần tử trong dãy mà không cần dùng đến những phần tử trước.

chuỗi:
Tổng của các phần tử trong một dãy.

biến tích lũy:
Biến được dùng để tích tụ kết quả từng ít một.

khái quát hóa:
Cách làm chương trình linh hoạt hơn, chẳng hạn bàng việc thay thế một giá trị cụ thể bằng một biến có thể nhận giá trị bất kì.

10. Bài tập

Ta đã thấy các dãy Fibonacci, F, vốn được định nghĩa theo cách truy hồi như sau

FFi–Fi–2

Để bắt đầu, bạn phải chỉ định hai phần tử đầu tiên, nhưng một khi có hai phần tử này rồi, bạn có thể tính toàn bộ các phần tử còn lại. Dãy Fibonacci thông dụng nhất khởi đầu với F= 1F= 1.

Hãy viết một đoạn mã lệnh có tên là fibonacci2 trong đó dùng một vòng lặp để tính 10 phần tử đầu tiên của dãy Fibonacci. Điều kiện cuối của chương trình là gán phần tử thứ 10 cho ans.

Bây giờ hãy khái quát hóa chương trình để nó tính phần tử thứ n với n bất kì, kèm theo điều kiện đầu là bạn phải đặt giá trị cho n trước khi chạy chương trình. Để đơn giản, tạm thời ta giả sử rằng n lớn hơn 2.

Gợi ý: bạn sẽ phải dùng hai biến để theo dõi hai phần tử liền trước của dãy. Có thể đặt tên chúng là prev1prev2. Ban đầu, prev1 = F1 còn prev2 = F2. Ở cuối mỗi lượt lặp, bạn sẽ phải cập nhật prev1prev2; hãy tính cẩn thận thứ tự cập nhật!

Hãy viết một tập tin lệnh có tên fib_plot trong đó lặp i từ 1 đến 20, dùng fibonacci2 để tính số Fibonacci, rồi chấm Fi với mỗi i dưới dạng một loạt điểm tròn màu đỏ.

4 bình luận

Filed under MatLab, Mô hình hóa

4 responses to “Chương 3. Vòng lặp

  1. Pingback: Chương 1. Các biến và giá trị | Blog của Chiến

  2. Pingback: Chương 11: Tối ưu hóa và nội suy | Blog của Chiến

  3. Pingback: Chương 6: Tìm nghiệm | Blog của Chiến

  4. Pingback: Mô hình hóa hiện tượng vật lý bằng MATLAB | Blog của Chiến

Bình luận về bài viết này