BÁO CÁO KẾT QUẢ
NGHIÊN CỨU, ỨNG DỤNG SÁNG KIẾN
1. Lời giới thiệu
Ngày nay Công nghệ thông tin (CNTT) thúc đẩy mạnh mẽ cuộc đổi mới trong
giáo dục, tạo ra công nghệ giáo dục (Educational Technology) với nhiều thành tựu
rực rỡ. CNTT làm thay đổi nội dung, hình thức và phương pháp dạy học một cách
phong phú. Trong thời đại thông tin bùng nổ ngày nay, việc lập được các chương
trình tự hoạt động cho máy tính, máy gia dụng là cần thiết. Và để làm được việc
đó cần có một quá trình nghiên cứu, học tập về ngôn ngữ lập trình lâu dài, qua đó
nhà lập trình có thể chọn một ngôn ngữ lập trình thích hợp.
Tin học là một môn học mới ở các trường phổ thông nên học sinh còn nhiều
bỡ ngỡ khi tiếp cận với môn học này. Nội dung tin học lập trình lớp 11 là một nội
dung mới lạ đối với đa số học sinh với nhiều khái niệm, thuật ngữ, cấu trúc dữ
liệu mà học sinh mới được tiếp xúc lần đầu. Chính vì vậy mà học sinh dễ mắc sai
lầm khi lập trình giải quyết các bài toán. Nguyên nhân dẫn đến những khó khăn
mà học sinh thường gặp là rất phong phú nhưng có thể thấy một số nguyên nhân
chính sau đây:
+ Học sinh thường gặp khó khăn khi xác định bài toán.
+ Khó liên hệ phương pháp giải một bài toán trong toán học với thuật giải
trong tin học.
Tuy nhiên mọi thứ điều có điểm khởi đầu của nó, với học sinh việc học ngôn
ngữ lập trình Turbo Pascal là khởi đầu cho việc tiếp cận ngôn ngữ lập trình bậc
cao, qua đó giúp các em hình dung được sự ra đời, cấu tạo, hoạt đông cũng như
ích lợi của các chương trình hoạt động trong máy tính, các máy tự động… Qua đó
giúp các em có thêm một định hướng, một niềm đam mê về tin học, về nghề
nghiệp mà các em chọn sau này.
Trong quá trình lập trình giải các bài toán học sinh thường mắc rất nhiều lỗi,
thậm chí có những lỗi các em mắc phải nhiều lần do không hiểu nguyên nhân xuất
hiện lỗi. Vì vậy trong nội dung đề tài này tôi nêu ra một số lỗi phổ biến các em
thường mắc phải và cách sửa các lỗi này.
Tuy vậy, đối với đối tượng học sinh khá giỏi, đa phần các em rất hào hứng
với việc học lập trình, cụ thể là ngôn ngữ lập trình Turbo Pascal. Do đó trong đề
tài này tôi cũng trình bày thêm một số lưu ý cũng như kinh nghiệm dạy lập trình
Pascal đề giúp học sinh có thể hiểu bài một cách nhanh chóng, nắm chắc kiến thức
và kĩ năng lập trình Pascal và một số ví dụ mở rộng, nâng cao với đối tượng học
sinh này.
2. Tên sáng kiến: “Một số kinh nghiệm khi dạy lập trình Pascal”.
3. Tác giả sáng kiến
Họ và tên: Đào Thị Loan
Địa chỉ: Trung tâm GDTX Tỉnh Vĩnh Phúc
Số điện thoại: 0916.806.698. E_mail:
[email protected]
4. Chủ đầu tư tạo ra sáng kiến: Đào Thị Loan
5. Lĩnh vực áp dụng sáng kiến
Lĩnh vực có thể áp dụng sáng kiến: Môn Tin học.
Vấn đề mà sáng kiến giải quyết: Kinh nghiệm khi dạy lập trình Pascal.
6. Ngày sáng kiến được áp dụng lần đầu:
Sáng kiến áp dụng năm học 2017-2018
7. Mô tả bản chất của sáng kiến
7.1. Nội dung sáng kiến
7.1.1. Các dạng bài tập cơ bản
7.1.1.1. Bài tập về viết thuật toán
Theo định nghĩa trong sách giáo khoa Tin học 10, thuật toán là một dãy hữu
hạn các thao tác được sắp xếp theo một trình tự xác định, sao cho sau khi thực
hiện dãy thao tác ấy từ INPUT ta nhận được OUTPUT. Nói cách khác, trình bày
thuật toán tức là chỉ ra các bước cần thực hiện để đi đến kết quả.
Việc trình bày thuật toán trước khi viết chương trình là hết sức quan trọng.
Thuật toán đúng thì chương trình mới có khả năng đúng, còn một thuật toán sai
chắc chắn là cho một chương trình sai. Tuy nhiên đối với phần lớn học sinh
thường bỏ qua bước này do tâm lý học sinh không thích các loại bài tập như thế.
Trong nhiều trường hợp tưởng như không cần thuật toán cụ thể học sinh vẫn
viết được chương trình. Thực tế thuật toán đó không được viết ra nhưng đã hình
thành sẵn trong đầu người viết.
Với đa số học sinh hiện nay, cần phải dành một lượng thời gian thích hợp để
rèn luỵên loại bài tập này. Phải làm sao cho việc viết thuật toán trở thành kĩ năng
để khi các em lập trình trên máy, tuy không cần viết thuật toán ra song các em có
thể hình dung được thuật toán đó trong đầu. Cần phải tạo cho các em có ý thức khi
viết một chương trình Pascal là phải tuân thủ theo trình tự sau:
Bài toán Xây dựng thuật toánViết chương trình
Ví dụ: Có n hộp có khối lượng khác nhau và một cái cân dĩa. Hãy chỉ ra cách
cân để tìm được hộp nặng nhất.Với bài toán trong thực tế như trên ta có thể phát
biểu lại dưới dạng bài toán trong toán học như sau: Cho tập hợp A có số phần tử
hữu hạn. Tìm phần tử lớn nhất trong tập A nói trên. Khi đó ta có thể trình bày
thuật toán như sau:
1. Nếu chỉ có 1 hộp thì đó chính là hộp nặng nhất và kết thúc.
2. Nếu số hộp n>1 thì
Chọn 2 hộp bất kì và đặt lên bàn cân.
Giữ lại hộp nặng hơn và cất hộp nhẹ đi chỗ khác.
3. Nếu không còn hộp chưa được cân thì chuyển sangbước 5, ngoài ra:
Chọn một hộp bất kì và để lên dĩa cân còn trống
Giữ lại hộp nặng hơn, cất hộp nhẹ sang chỗ khác
4. Trở lại bước 3
5. Hộp còn lại trên cân là hộp nặng nhất và kết thúc.
7.1.1.2. Bài tập về đọc hiểu chương trình
Loại bài tập này sẽ giúp phát triển tư duy, giúp học sinh hiểu bài, nhất là khi
dạy các cấu trúc lệnh. Đối với dạng bài tập này, giáo viên nên hướng dẫn các em
thực hiện tuần tự từng lệnh theo từng câu lệnh cụ thể.
Ví dụ: Cho biết kết quả khi thực hiện chương trình sau:
Program vd2;
Uses crt;
Var i:integer;
Begin
Clrscr;
I:=7;
While i>1 do
Begin
If (i mod 2)<>0 then i:=i*3+1
Else i:=i div 2;
Writeln(i);
End;
Readln;
End.
7.1.1.3. Bài tập về sửa lỗi chương trình
Ví dụ 3: Để tìm số lớn nhất trong 3 số a,b,c được nhập vào từ bàn phím, có
người đã viết chương trình như sau:
Program vd3;
Uses crt;
Var a,b,c:integer;
Begin
Clrscr;
Write(‘nhap vao 3 so:’);
Readln(a,b,c);
If a
‘ ’. nếu đúng thì đến bước 3, sai đến bước 5.
Bước 3: Tword:=Tword+xau[i]
Bước 4: Kiểm tra i<=length(xau). Đúng thì tăng i lên 1 và quay lại bước 2;
sai thì đến bước 8.
Bước 5: kiểm tra Tword<> ‘’. Đúng thì xauM:=xauM+Tword+ ‘ ’ ; gán
Tword= ‘’ và quay lại bước 4. Sai thì chuyển đến bước 6.
Bước 6: Kiểm tra Tword<> ‘’. Đúng thì gán xauM:=xauM+Tword; sai thì
xoá kí tự trống ở vị trí length(xauM) của xauM.
Bước 7: gán xau:=xauM;
Bước 8: kết thúc.
Chương trình có thể được viết như sau:
Program vd5;
Uses crt;
Var xau, xauM,Tword:string;
I:byte;
Begin
Wrire(‘nhap vao mot xau ki tu’);readln(xau);
xauM:= ‘’;Tword:= ‘’;
for i:=1 to length(xau) do
if xau[i]<> ‘ ’ then Tword:=Tword+xau[i];
else
begin
if Tword<> ‘’ then xauM:=xauM+Tword+ ‘ ’;
Tword:= ‘’;
End;
If Tword <> ‘’ then xauM:=xauM+Tword
Else xau:=xauM;
Write(‘Xau sau khi xoa cac ki tu trang thua la:’,xau);
Readln;
End.
Tuy nhiên bài toán trên ngoài cách giải trên ta có thể sử dụng thuật toán khác
để giải. Thuật toán như sau:
Bước 1: Xoá các kí tự trong thừa ở đầu.
Sử dụng vòng lặp while: while xau[1]= ‘ ’ do delete(xau,1,1);
Bước 2: Xoá các kí tự trống ở cuối.
Sử
dụng
vòng
lặp
while:
while
xau[length(xau)]=
‘
’
do
delete(xau,length(xau),1);
Bước 3: xoá các kí tự trống thừa giữa các từ.
Kiểm tra 2 kí tự liền kề nhau có hơn 1 kí tự trống thì xoá kí tự trống.
While pos( ‘ ’,xau)<>0 do delete(xau,pos( ‘ ’,xau),1);
Học sinh có thể viết chương trình theo thuật toán 2.
7.1.1.6. Giải bài toán trong một trường hợp riêng, yêu cầu học sinh phát
hiện thiếu sót để từ đó hoàn thiện chương trình
Ví dụ 6: Viết chương trình đếm và in ra các số trong 1 xâu đã cho.
Cho đoạn chương trình giải quyết công việc trên như sau:
I:=1; dem:=0;
While i<=length(xau) do
Begin
If (xau[i]>= ‘0’) and (xau[i]<= ‘9’) then
Begin
xauM:= ‘’;
while (xau[i]>= ‘0’) and (xau[i]<= ‘9’) do
begin
xauM:=xauM+xau[i];
i:=i+1;
end;
dem:=dem+1;
val(xauM,a[dem],n);
i:=i-1;
End;
I:=i+1;
End;
Write(‘xau co ’,dem, ‘ so la:’);
For i:=1 to dem-1 do write(a[i], ‘,’);
Write(a[dem]);
Đối với bài tập này, giáo viên yêu cầu nhận xét chương trình đã thực hiện
đúng hay chưa, có đúng đối với tất cả các trường hợp hay không?
Học sinh có thể phát hiện chương trình chỉ đúng với xâu chứa các số thông
thường, còn nếu xâu chứa số thực thị chương trình chưa cho kết quả đúng. Từ
nhận xét đó giáo viên hướng dẫn các em bổ sung và chỉnh sửa lại chương trình.
7.1.1.7. Phân chia một bài toán thành nhiều bài toán nhỏ
Trong thực tế chúng ta thường gặp những vấn đề lớn mà với sức của một
người thì không thể giải quyết được khi gặp những vấn đề như vậy, ta thường nhờ
bạn bè, người thân giúp một tay, mỗi người lo một phần việc. Khi giải một bài
toán, ta thường chia bài toán lớn ban đầu thành nhiều bài toán con để việc giải bài
toán ban đầu trở nên dễ dàng hơn… Như vậy khi cần giải quyết một vấn đề nào
đó bằng máy tính, để viết một chương trình phức tạp ta có thể viết từng phần
chương trình giải quyết từng vấn đề nhỏ.
Việc phân chia một bài toán thành nhiều bài toán nhỏ sẽ giúp cho việc giải
quyết bài toán mạch lạc, vịêc kiểm tra sai sót thuận tiện, có thể thấy kết quả ở
từng bước và có thể điều chỉnh kịp thời.
7.1.2. Một số bài tập tham khảo
7.1.2.1. Hệ đếm
Trong một cuộc truy tìm một xe ôtô chở hàng lậu, nguồn tin đầu tiên cho
biết: số của biển xe là số có 3 chữ số đối xứng.
(Một số có n chữ số trong một hệ đếm nào đó được gọi là đối xứng nếu chữ
số thứ 1 giống với chữ số thứ n, chữ số thứ 2 giống với chữ số thứ n - 1, ... ).
Sau đó cảnh sát nhận được thêm thông tin: số biển số là một số nguyên tố.
Cảnh sát dựa vào dự đoán của một chuyên gia tin học đưa ra sau khi phân tích các
nguồn tin và xác định tập các số có thể là số của biển số: biển số nếu viết trong hệ
nhị phân cũng là một số đối xứng. Nhờ vậy mà cảnh sát đã bắt đúng đối tượng.
Hãy cho biết các số mà chuyên gia tin học đã xác định mà số biển xe mà ông ta đã
dự đoán đúng.
Ý tưởng:
Có thể có nhiều cách giải khác nhau nhưng tất cả đều cần phải giải quyết các
vấn đề:
-
Kiểm tra xem một số có là số nguyên tố hay không?
-
Tìm dạng biểu diễn nhị phân của một số.
-
Kiểm tra một số có là đối xứng không?
-
Chọn các số đối xứng (trong hệ thập phân) thích hợp để kiểm tra.
Chương trình:
PROGRAM BienSo;
Uses crt;
Var m,i,j,k,n,l:integer;
a,b: array[1..14] of integer;
Procedure ChuyenMa(i:integer);
Begin
i:=0;
while i<>0 do
begin
i:=i+1;
a[i]:=i mod 2;
i:=i div 2;
end;
End;
Function NgTo(i:integer):boolean;
var b:boolean;
j:integer;
Begin
b:=true; j:=1;
while (ja[l-i+1] then b:=false
else i:=i+1;
end;
DoiXung:=b;
End;
BEGIN
clrscr;
b[1]:=1; b[2]:=3;
b[3]:=7; b[4]:=9;
for m:=1 to 4 do
for k:=0 to 9 do
begin
n:=101*b[m] + 10*k;
if ngto(n) then
begin
chuyenma(n);
if doixung then
begin
write('Bien so can tim: ',n:5,' Ma nhi phan la ');
for j:=1 to l do write(a[j]:2);
writeln;
end
else
writeln('So ngto doi xung nhung ma nhi phan khong doi xung
',n:5);
end;
end;
END.
7.1.2.2. Quan hệ
Có N người mang tên tương ứng là 1, 2, ..., N và tình trạng quen biết của N
người này được cho bởi mảng đối xứng A[1..N,1..N] trong đó A[i,j] = A[j,i] = 1
nếu i quen j và bằng 0 nếu i không quen j. Hãy xét xem liệu có thể chia N người
đó thành 2 nhóm mà trong mỗi nhóm hai người bất kì đều không quen nhau?
Dữ liệu vào được cho bởi file QUANHE.INP trong đó dòng thứ nhất ghi số
nguyên dương N <= 100, trong N dòng tiếp theo, dòng thứ i ghi N số A[i,1], ...,
A[i,N].
Kết quả ghi ra file QUANHE.OUT như sau:
o
Nếu không có thể , ghi dòng chữ KHONG THE
o
Nếu có thể, ghi ra hai dòng, dòng thứ nhất tên những người thuộc
nhóm 1, dòng thứ hai tên những người thuộc nhóm 2.
Gợi ý:
Lập mảng XEP[1..N] khởi tạo mọi giá trị bằng 0. Bắt đầu chia nhóm từ
người thứ nhất cho tới người thứ N. Khi xét người thứ i, những khả năng sau có
thể xảy ra:
-
Nếu XEP[i] = 0 (chưa được xếp nhóm) thì xếp vào nhóm 1(XEP[i] =
1) và xếp những người j quen i vào nhóm 2 (cho XEP[j] =2).
-
Nếu XEP[i] = 1 và trong số những người quen i có một người j mà
XEP[j] cũng bằng 1 thì kết luận không xếp được.
-
Nếu XEP[i] = 2 và trong số những người quen i có một người j mà
XEP[j] cũng bằng 2 thì kết luận không xếp được.
7.1.3. Một số lỗi sai thường gặp của học sinh trong lập trình Pascal
Trong quá trình học lập trình học sinh thường gặp phải một số lỗi sau:
1. Khai báo sai miền chỉ số cho dữ liệu kiểu mảng.
Ví dụ 1: Nhập vào một mảng số nguyên gồm các số lớn hơn 3 và nhỏ hơn
100. In mảng vừa nhập.
Học sinh khai báo mảng như sau:
Var a: array[3..100] of integer;
2. Sử dụng dấu; sai vị trí.
Trước Else không có dấu chấm phẩy.
Sử dụng dấu chấm phẩy sau từ khoá do trong các câu lệnh lặp câu lệnh lặp
rỗng không làm việc gì cả.
3. Giá trị biến điều khiển vượt quá miền chỉ số của mảng.
Ví dụ 2: Nhập vào một dãy số gồm 7 phần tử và cho biết dãy vừa nhập có tạo
thành cấp số cộng không?
Học sinh lập trình giải bài toán trên như sau:
Var a: array[1..7] of integer; i,d:integer; kt:boolean;
Begin
Write(‘nhap day so:’);
For i:=1 to 7 do
Begin
Write(‘a[’ ,i, ‘]’);
Readln(a[i]);
End;
d:=a[2]-a[1];kt:=true;i:=1;
while (kt) and (i<=7) do
if (a[i]-a[i-1]<>d) then kt:=false
else i:=i+1;
if kt then writeln(‘Day so tao thanh cap so cong!’)
else writeln(‘Day so khong tao thanh cap so cong!’);
readln
End.
Khi thực hiện chương trình trên, chương trình dịch không báo lỗi nhưng kết
quả khi thực hiện chương trình sẽ bị sai lệch. Khi thực hiện từng bước chương
trình ta có thể khắc phục lỗi trên bằng cách gán lại giá trị ban đầu cho biến đếm
i=2.
4. Tràn số do kết quả tính toán vượt quá giới hạn.
Function GT(n:integer):integer;
Var i,t:integer;
Begin
T:=1;
For i:=2 to n do t:=t*i;
Gt:=t;
End;
Begin
Write(‘GT(8)=’, GT(8));
Readln;
End.
Khi thực hiện chương trình GT(8)=-25126 là sai vì thực tế 8!=40320
Lỗi này do khai báo hàm trả về số nguyên nên miền giá trị tối đa là 32767
5. Dùng cùng tên biến điều khiển cho các vòng lặp for lồng nhau.
Ví dụ 3: Tính tổng S=1k+2k+….+nk
Học sinh lập trình giải bài toán trên như sau:
S:=0;
For i:=1 to n do
Begin
T:=1;
For i:=1 to k do
T:=T*i;
S:=S+T;
End;
Đoạn chương trình trên có thể lặp vô tận khi kết thúc vòng lặp con i luôn
nhận giá trị bằng k.
Để khắc phục lỗi này, chỉ cần chú ý các vòng lặp lồng nhau phải sử dụng
biến điều khiển khác nhau.
6. Không phân biệt được hằng xâu và biến: học sinh cần phải chú ý hằng xâu
đặt trong cặp nháy đơn còn biến thì không cần đặt trong cặp nháy đơn.
7. Sử dụng tên hàm làm biến cục bộ.
Do lệnh trả kết quả cho tên hàm rất giống một lệnh gán bình thường nên học
sinh thường nhầm tên hàm là biến cục bộ. Vì vậy khi viết chương trình để tiết
kiệm biến cục bộ học sinh đã sử dụng tên hàm làm biến cục bộ.
Function GT(n:integer):Longint;
Var i:integer;
Begin
For i:=2 to n do GT:=GT*i;
End;
Trong thân hàm đã sử dụng tên hàm làm biến cục bộ nên khi biên dịch sẽ báo
lỗi gọi hàm nhưng thiếu tham số do chương trình hiểu GT:=GT*i là lời gọi đệ qui.
Để tránh lỗi này cần lưu ý với học sinh: để trả kết quả cho hàm (không đệ
quy), tốt nhất nên tính kết quả hàm vào một biến cục bộ, trước khi kết thúc ta mới
gán tên hàm bằng giá trị biến này để trả giá trị về cho hàm.
8. Không hiểu nguyên tắc làm tròn số đối với số thực.
Trong toán học:
3
1
1
1
2
2
2
2
Tuy nhiên trong Pascal biểu thức sau sẽ cho kết quả sai:
If
3/sqrt(2)=1/sqrt(2)+1/sqrt(2)+1/sqrt(2)
then
write(‘Dung!’)
else
write(‘sai!’);
Khi thực hiện vế trái máy tính chỉ tính sai số 1lần, còn vế phải chịu sai số 3
lần. Do đó kết quả khi thực hiện là không giống nhau. Đây chỉ là 1 nguyên nhân
khiến cho quá trình tính toán gần đúng trên máy tính thành tính toán sai. Để tránh
điều này bạn nên tuân thủ theo các qui tắc dưới đây:
So sánh bằng nên dùng biểu thức a b
Const e=0.0001;
….
If abs(a-b)=,>,<=,<,=,<>
7.1.4. Kết quả nghiên cứu
Khi thực hiện thực nghiệm qua các đối tượng học sinh đã nêu trên, đa số các
em tránh được các lỗi thường gặp khi học lập trình Pascal.
Một số không ít học sinh có tiến bộ rõ rệt khi viết các chương trình có sử
dụng lập trình có cấu trúc.
Nâng cao việc yêu thích học tin học đối với một bộ phận học sinh và một số
em có định hướng nghề nghiệp sau này.
7.2. Về khả năng áp dụng của sáng kiến
Những kinh nghiệm trong lập trình Pascal có thể áp dụng với các giáo viên,
học sinh dạy và học lập trình ở cấp THPT.
8. Những thông tin cần được bảo mật
Không
9. Các điều kiện cần thiết để áp dụng sáng kiến
- Sự quan tâm, chỉ đạo của cấp trên trước những vấn đề đổi mới của ngành
giáo dục; trang bị đầy đủ cơ sở vật chất.
- Cán bộ quản lý, giáo viên, nhân viên tâm huyết, nhiệt tình, trách nhiệm
không ngừng học hỏi.
- Cơ chế khen thưởng kịp thời phù hợp.
10. Đánh giá lợi ích thu được hoặc dự kiến có thể thu được do áp dụng
sáng kiến theo ý kiến của tác giả và theo ý kiến của tổ chức, cá nhân
10.1. Đánh giá lợi ích thu được hoặc dự kiến có thể thu được do áp dụng
sáng kiến theo ý kiến của tác giả
Sau một thời gian tiến hành giảng dạy lập trình Pascal tôi nhận thấy những kinh
nghiệm trong lập trình Pascal có tác dụng rất hữu ích cho người dạy và người học.
Đối với giáo viên:
+ Chủ động trong mọi tình huống dạy học, tiết kiệm thời gian, chí phí.
+ Đưa ra các phương pháp dạy phù hợp với các đối tượng học sinh.
+ Giúp học sinh chủ động trong quá trình học lập trình Pascal.
Đối với học sinh:
+ Tích cực tham gia học tập, phát huy tối đa tính chủ động sáng tạo, học tập
của học sinh.
+ Giúp học sinh hứng thú hơn với môn học.
+ Chủ động hơn trong khắc phục các lỗi khi lập trình Pascal.
+ Dễ dàng nắm bắt kiến thức chủ động hơn trong học tập.
10.2. Đánh giá lợi ích thu được hoặc dự kiến có thể thu được do áp dụng
sáng kiến theo ý kiến của tổ chức, cá nhân
Sáng kiến “Một số kinh nghiệm khi lập trình Pascal” giúp cho giáo viên, học
sinh chủ động hơn trong quá trình lập trình bằng ngôn ngữ Pascal, nâng cao chất
lượng dạy và học trong nhà trường.
11. Danh sách những tổ chức/cá nhân đã tham gia áp dụng sáng kiến lần đầu:
Số TT
1
2
Tên tổ chức/cá nhân
Các học viên học lập
trình Pascal
Học sinh lớp 11
Thủ trưởng đơn vị
Địa chỉ
Phạm vi/Lĩnh vực
áp dụng sáng kiến
TT GDTX Tỉnh Vĩnh Phúc Học lập trình Pascal
TT GDTX Tỉnh Vĩnh Phúc Học lập trình Pascal
CHỦ TỊCH HỘI ĐỒNG
SÁNG KIẾN CẤP CƠ SỞ
Vĩnh Yên, ngày
tháng 01 năm 2019
Tác giả sáng kiến
Đào Thị Loan