CHƯƠNG 3: BÀI TOÁN ỨNG DỤNG
3.1. Ứng dụng F* vào các bài toán lập trình
3.1.2. Ứng dụng trong lập trình sắp xếp mảng nhanh (Quick sort)
Qua bài toán về BubleSort, chúng ta có thể chưa thấy rõ được ưu điểm của F*. Tuy nhiên với bài toán lập trình sắp xếp mảng dưới đây, ta có thể thấy F* giúp ích như thế nào. Bắt đầu bài toán chúng ta sử dụng một hàm xác thực tính đúng đắn của mảng, các phần tử trong mảng đã được sắp xếp theo thứ tự tăng dần hay chưa(sau khi đã được sắp xếp (sorted)) như sau:
val sorted:listint-> Tot bool letrec sorted l =match l with
|[]->true
|[x]->true
|x::y::xs -> x <= y && sorted (y::xs)
Hàm trên xác nhận rằng các phần tử trong danh sách l đã được sort đúng:
- Mảng rỗng: trả về kết quả true
- Mảng có một phần tử [x]: trả về kết quả true
- Mảng có nhiều phần tử: lấy ra 2 phần tử đầu mảng và xác nhận rằng x luôn nhỏ hơn hoặc bằng y và tiếp tụcsắp xếp trong danh sách còn lại append (y, xs)
Tiếp theo ta sử dụng bổ đề (lemma) để kiểm chứng bảo toàn dữ liệu sau khi sử dụng hàm sort: với mọi phần tử trong danh sách ban đầu đều thuộc danh sách sau khi sắp xếp là:
sorted(sort l)/\ forall i. mem i l <==> mem i (sort l)
Với hai hàm kiểm tra trên, ta có thể yên tâm rằng các hàm sắp xếp mà chúng ta viết sau này có thể kiểm chứng là đúng đắn.
Giới thiệu về bài toán quick sort:
Trước tiên chúng ta lấy một phần tử làm điểm chốt và phân chia danh sách dữ liệu thành hai mảng sao cho một mảng chứa các số lớn hơn điểm chốt và một mảng chứa các số nhỏ hơn điểm chốt. Sau đó chúng ta tiếp tục sử dụng truy hồi tới hai mảng còn lại tương tự như mảng dữ liệu đầu tiên và ghép tất cả các mảng sau khi đã sắp xếp lại thành mảng chứa dữ liệu ban đầu đã được sắp xếp lại. Dưới đây ta có thể viết một hàm
QuickSort trong F#:
module QuickSort
letrec quicksort list = match list with
| [] -> [] //Nếu danh sách rỗng -> Return
| pivot::tl ->//Lấy ra phần tử đầu tiên trong mảng là phần tử chốt let lo = //Lấy ra các phần tử nhỏ hơn pivot
tl
|> List.filter (fun e -> e < pivot)
|> quicksort // Tiếp tục sắp xếp trong mảng lo let hi = //Lấy ra các phần tử lớn hơn pivot
tl
|> List.filter (fun e -> e >= pivot)
|> quicksort // Tiếp tục sắp xếp trong mảng hi // Nối các mảng sau khi đã được sắp xếp với nhau
List.concat [lo; [pivot]; hi]
printfn"%A" (quicksort [2;7;12;16;19;11;1])
Xây dựng hàm QuickSort trong C#:
namespace QuickSort {
classProgram {
staticpublicint Partition(int[] numbers, int left, int right) {
int pivot = numbers[left];
while (true) {
while (numbers[left] < pivot) left++;
while (numbers[right] > pivot) right--;
if (left < right) {
int temp = numbers[right];
numbers[right] = numbers[left];
numbers[left] = temp;
}
elsereturn right;
} }
staticpublicvoid QuickSort(int[] arr, int left, int right) {
if (left < right) {
int pivot = Partition(arr, left, right);
if (pivot > 1)
QuickSort(arr, left, pivot - 1);
if (pivot + 1 < right)
QuickSort(arr, pivot + 1, right);
} }
//Main test
staticvoid Main(string[] args) {
int[] numbers = { 3, 8, 7, 5, 2, 1, 9, 6, 4, 12, 20, 11 };
int len = numbers.Length;
QuickSort(numbers, 0, len - 1);
for (int i = 0; i < len; i++) Console.Write(numbers[i] + " , ");
Console.ReadLine();
} }
}
Với 2 đoạn mã về QuickSort được viết trên 2 nền tảng khác nhau, đoạn mã trên F#
có cách đọc hiểu dễ dàng, cách viết ngắn gọn hơn tuy nhiên không thể đảm bảo được rằng dữ liệu sau khi qua các hàm biến đổi, sắp xếp được bảo toàn. F* cho phép lập trình viên hỗ trợ việc đó như sau:
1module QuickSort
// Khai báo hàm mem để kiểm tra tính toàn vẹn của dữ liệu 2val mem:'a ->list 'a -> Tot bool
3letrec mem a l =match l with 4 |[]->false
5 | hd::tl ->hd=a || mem a tl
// Khai báo hàm append để ghép 2 mảng dữ liệu 6val append :list 'a ->list 'a -> Tot (list 'a) 7letrec append l1 l2 =match l1 with
8 |[]-> l2
9 | hd::tl -> hd::append tl l2
// Khai báo hàm append mem để kiểu tra tính toàn vẹn dữ liệu sau khi ghép 2 danh sách
10val append_mem: l1:list 'a 11 -> l2:list 'a
12 -> Lemma (requires True)
13 (ensures (forall a. mem a (append l1 l2)=(mem a l1 || mem a l2))) 14 [SMTPat (append l1 l2)]
15letrec append_mem l1 l2 =match l1 with
|[]->()
16 | hd::tl -> append_mem tl l2
// Khai báo hàm length để kiểm tra độ dài của mảng 17val length:list 'a -> Tot nat
18letrec length l =match l with 19 |[]->0
20 |_::tl ->1+ length tl
// Khai báo hàm sorted để kiểm tra các phần tử trong mảng được sắp xếp hay chưa
21val sorted:listint-> Tot bool 22letrec sorted l =match l with 23 |[]->true
24 |[x]->true
25 | x::y::xs -> x <= y && sorted (y::xs)
// Khai báo partition để chia một danh sách dữ liệu thành 2 danh sách mới 26val partition:('a -> Tot bool)->list 'a -> Tot (list 'a *list 'a)
27letrec partition f =function
28 |[]->[],[]
29 | hd::tl ->
30 let l1, l2 = partition f tlin 31 if f hd
32 then hd::l1, l2 33 else l1, hd::l2
// Khai báo partition_lemma để kiểm tra tính toàn vẹn dữ liệu sau khi sử dụng hàm partition
34val partition_lemma:f:('a -> Tot bool) 35 -> l:list 'a
36 -> Lemma (requires True)
37 (ensures ((length (fst (partition f l))+ length (snd (partition f l)) = length l 38 /\ (forall x. mem x (fst (partition f l))==> f x)
39 /\ (forall x. mem x (snd (partition f l))==> not (f x))
40 /\ (forall x. mem x l =(mem x (fst (partition f l))|| mem x (snd (partition f l)))))))
41 [SMTPat (partition f l)]
42letrec partition_lemma f l =match l with 43 |[]->()
44 | hd::tl-> partition_lemma f tl
// Khai báo hàm sorted_concat_lemma để kiểm tra việc sắp xếp dữ liệu giữa 2 mảng và phần tử chốt
45val sorted_concat_lemma: l1:listint{sorted l1}
46 -> l2:listint{sorted l2}
47 -> pivot:int
48 -> Lemma (requires ((forall y. mem y l1 ==> not (pivot <= y)) 49 /\ (forall y. mem y l2 ==> pivot <= y)))
50 (ensures (sorted (append l1 (pivot::l2)))) 51 [SMTPat (sorted (append l1 (pivot::l2)))]
52letrec sorted_concat_lemma l1 l2 pivot =match l1 with 53 |[]->()
54 | hd::tl -> sorted_concat_lemma tl l2 pivot 55let cmp i j = i <= j
// Khai báo hàm dùng để sắp xếp dữ liệu
56val sort: l:listint-> Tot (m:listint{sorted m /\ (forall i. mem i l = mem i m)})(decreases (length l))
57letrec sort l =match l with 58 |[]->[]
59 | pivot::tl ->
60 let hi, lo = partition (cmp pivot)tlin 61 append (sort lo)(pivot::sort hi)
Hình 3.2: Kết quả cho bài toán sắp xếp nhanh