Thông điệp ngắn
- RT @TelegraphNews Netflix lets its staff take as much holiday as they want, whenever they want – and it works http://bit.ly/ajRs6J
- Google Making Extraordinary Counteroffers To Stop Flow Of Employees To Facebook http://t.co/M2kCTgj via @techcrunch
- 'Power corrupts. PowerPoint corrupts absolutely." http://bit.ly/cWQUjP
- Lecture on lectures http://bit.ly/d6ODHd
- How panhandlers use free credit cards - thestar.com http://t.co/j3pSXMB
Phản hồi mới
- Administrator on HM5 — Định lý Vapnik-Chervonenkis cho mô hình giả thuyết không nhất quán
- Na K54 on HM5 — Định lý Vapnik-Chervonenkis cho mô hình giả thuyết không nhất quán
- Na K54 on VC
- xuanlong on HM5 — Định lý Vapnik-Chervonenkis cho mô hình giả thuyết không nhất quán
- Duy on VC
- xuanlong on HM5 — Định lý Vapnik-Chervonenkis cho mô hình giả thuyết không nhất quán
- Ngô Quang Hưng on HM5 — Định lý Vapnik-Chervonenkis cho mô hình giả thuyết không nhất quán
- Ngô Quang Hưng on HM5 — Định lý Vapnik-Chervonenkis cho mô hình giả thuyết không nhất quán
- XuanLong Nguyen on HM5 — Định lý Vapnik-Chervonenkis cho mô hình giả thuyết không nhất quán
- XuanLong Nguyen on HM5 — Định lý Vapnik-Chervonenkis cho mô hình giả thuyết không nhất quán
-
Bài mới
Thư khố
Chuyên Mục
- Âm Nhạc (39)
- Ảnh hưởng của CNTT (3)
- Bảo mật và mật mã học (67)
- Blog cầu (5)
- Bơi (1)
- Các hệ thống máy tính (9)
- Các hội nghị KHMT (11)
- Công nghệ phần mềm (6)
- Chính trị trong ngành (29)
- Chưa phân loại (34)
- CNTT các nước và VN (47)
- Combinatorics (16)
- Cơ sở dữ liệu (2)
- Danh ngôn (12)
- Dành cho du học sinh (97)
- Games (2)
- Giáo dục (76)
- Giới thiệu sách (33)
- KHMT và Kinh Tế (1)
- KHMT và luật pháp (6)
- KHMT và sinh học (5)
- KHMT và triết học (3)
- Lập trình (3)
- Lịch sử Việt Nam (2)
- Lý thuyết mã hóa (2)
- Lý thuyết tính toán (53)
- Lý thuyết thông tin (15)
- Mạng máy tính (34)
- Mỹ quốc (12)
- Nghiên cứu nghiên kiếc (50)
- Nhân vật và sự kiện (127)
- Quả đất của ta (1)
- Thông báo (23)
- Thuật ngữ chuyên ngành (8)
- Thuật Toán (51)
- Tin tức đó đây (68)
- Toán Ứng Dụng (3)
- Toán tối ưu (5)
- Trang web hay (31)
- Trí tuệ nhân tạo (40)
- Vui – Giải Trí (216)
- Vượt định kiến (2)
- Xác suất & thống kê (49)
- Xuất bản (14)
Báo chí
Bảo mật
Blog Việt
Chưa phân loại
Giáo dục
Kỹ thuật
Khoa học khác
Kinh tế, luật pháp, xã hội
- A Tiny Revolution
- Andrew Sullivan.com
- Chicago’s Law Faculty
- Computing Chris
- Creative Capitalism
- Crooked Timber
- Daily Kos
- Freakonomics
- Free exchange
- Furdlog
- Instapundit
- Marginal Revolution
- Social Science Statistics
- Structured Procrastination
- The Becker-Posner Blog
- The Volokh Conspiracy
- Vietnam Quant. Society
- Đỗ Quốc Anh
Lý thuyết & thuật toán
Toán học
Vật Lý
Chương trình tự tái sinh
Một trong các bài tập rất hay cho người đang học lập trình (bất kể ngôn ngữ nào) là: hãy viết một chương trình in chính nó ra. Chú ý là phải in ra giống hệt, từng giòng từng chữ từng cái xuống hàng. Ở đây ta giả sử có thể viết chương trình bằng một tệp (file) thôi.
Nếu bạn chưa bao giờ nghĩ về các chương trình kiểu này, hãy dành vài chục phút nghĩ trước khi đọc các ví dụ dưới đây. Bạn sẽ thấy bài tập này liên quan mật thiết đến khái niệm “tự tham chiếu”, một trong những khái niệm trung tâm trong bài Chung qui chỉ tại Cantor.
Thử xét vài ví dụ sau:
main(){char *c="main(){char *c=%c%s%c;printf(c,34,c,34);}";printf(c,34,c,34);}import java.text.*;class a{public static void main(String x[]){char b[]={34}; char c[]={123};String s[]=new String[3];s[0]="import java.text.*;class a{2} public static void main(String x[]){2}char b[]={2}34};char c[]={2}123}; String s[]=new String[3];s[0]={1}{0}{1};s[1]=new String(b);s[2]=new String(c); System.out.println(MessageFormat.format(s[0],s));}}";s[1]=new String(b);s[2]= new String(c);System.out.println(MessageFormat.format(s[0],s));}}#!/usr/bin/perl $_=<<'eof';eval $_; print "#!/usr/bin/perln$_=<<'eof';eval $_;n${_}eofn" eofCác ví dụ trên tôi lấy ở đây, và ở đây. Dưới đây ta sẽ thiết kế một ví dụ cho riêng ta, không cần đi “chôm” về. Các trương trình tự in bản thân còn được gọi là Quine, họ của triết gia Willard van Orman Quine. Có hai câu hỏi liên quan:
Trước hết, ta bàn về nguyên tắc viết các chương trình tự in được bản thân. Ta sẽ làm việc trên một mô hình tổng quát (nhưng hoàn có thể hiện thực được với bất kỳ ngôn ngữ máy tính nào). Giả sử trong mô hình máy tính này có một vùng nhớ mà tất cả các chương trình đều lấy input từ đó và ghi ra đó được. Có hai quan sát nhỏ:
trong ngôn ngữ C chẳng hạn.
Ý tưởng của chương trình tự in bản thân (vào vùng nhớ chung) như sau: chương trình này có hai đoạn, đoạn A và đoạn B, nghĩa là nối A với B thì ta có toàn bộ chương trình.
Đoạn B làm công việc như sau:
Chú ý rằng, sau khi B hoạt động xong thì trong vùng nhớ chung chứa P(x) rồi đến x. Bạn nên tưởng tưởng viết một đoạn chương trình như B trong ngôn ngữ bạn thích.
Đoạn chương trình A thì làm gì? Nó sẽ ghi x vào vùng nhớ cho B đọc và ghi P(x), x. Thế A ghi gì? A ghi ra chính đoạn B ở trên!!!
Vì A ghi B vào vùng nhớ, A = P(B), và x = B. Còn B ghi ra “P(x)x” = “P(B)B” = “AB”, chính là chương trình ta đang viết.
Ta thử dùng nguyên tắc mô tả trong ví dụ trước để viết một chương trình C tự in bản thân. Lần thử đầu tiên, chương trình A ghi x vào biến buffer, chương trình B in ra đoạn chương trình P(x), sau đó in x. Kết quả đại khái như sau:
int main() { char* buffer = "printf(\"int main() {\\n char* buffer = %s;\\n %s\\n}\\n\", buffer, buffer);"; printf("int main() {\n char* buffer = \"%s\";\n %s\n}", buffer, buffer); }Lần thử này không thành công lắm, dù chương trình này in ra một chương trình gần giống nó. Vấn đề nằm ở cả đống ký tự thoát (escape characters ‘\’). Nó in thế này:
int main() { char* buffer = "printf("int main() {\n char* buffer = %s;\n %s\n}\n", buffer, buffer);"; printf("int main() {\n char* buffer = %s;\n %s\n}\n", buffer, buffer); }Rút kinh nghiệm, tôi tránh các escape characters và thay chúng bằng mã ASCII. Mã ASCII của ký tự xuống dòng (linefeed) là 10, và của ký tự ngoặc kép (“) là 34. Kết quả lần thử thứ hai cho đoạn chương trình:
int main() { char* buffer = "printf(\"int main() {%c char* buffer = %c%s%c;%c %s%c}\", 10, 10, 34, buffer, 34, 10, buffer, 10);"; printf("int main() {%c char* buffer = %c%s%c;%c %s%c}", 10, 34, buffer, 34, 10, buffer, 10); }Chương trình này in ra
int main() { char* buffer = "printf("int main() {%c char* buffer = %c%s%c;%c %s%c}", 10, 10, 34, buffer, 34, 10, buffer, 10);"; printf("int main() {%c char* buffer = %c%s%c;%c %s%c}", 10, 10, 34, buffer, 34, 10, buffer, 10); }Giống lắm rồi. Nhưng vẫn bị một escape character \ cho dấu ” của printf. Lý do là vì cả printf lẫn định nghĩa buffer đều dùng “, mà nếu bỏ ” vào trong định nghĩa buffer thì phải escape nó. Nhưng ta hoàn toàn có thể printf mà không dùng ” như sau:
int main() { char* buf1 = "char buf2[] = {'i', 'n', 't', ' ', 'm', 'a', 'i', 'n', '(', ')', ' ', '{', '%', 'c', ' ', 'c', 'h', 'a', 'r', '*', ' ', 'b', 'u', 'f', '1', ' ', '=', ' ', '%', 'c', '%', 's', '%', 'c', ';', '%', 'c', ' ', '%', 's', '%', 'c', '}', (char) 0}; printf(buf2, 10, 34, buf1, 34, 10, buf1, 10);"; char buf2[] = {'i', 'n', 't', ' ', 'm', 'a', 'i', 'n', '(', ')', ' ', '{', '%', 'c', ' ', 'c', 'h', 'a', 'r', '*', ' ', 'b', 'u', 'f', '1', ' ', '=', ' ', '%', 'c', '%', 's', '%', 'c', ';', '%', 'c', ' ', '%', 's', '%', 'c', '}', (char) 0}; printf(buf2, 10, 34, buf1, 34, 10, buf1, 10); }Bạn có thể dịch và chạy thử đoạn trên, in ra chương trình y hệt.
Chương trình tự tái sinh có nhiều ứng dụng thú vị. “In bản thân” chỉ là một ví dụ của sự tái sinh.
Các virus máy tính đều có chức năng tái sinh “y chang” này, nhưng thay vì in ra stdout, chúng tự copy bản thân qua một địa chỉ mới (trong bộ nhớ, qua mạng, hay xuống đĩa cứng). Trong bài tiến hóa số tôi có đề cập đến các chương trình máy tính tự tái sinh dùng để mô phỏng tiến hóa của các loại “vi khuẩn số” đơn giản.
Trong tương lai, bạn có thể tưởng tượng các chương trình phức tạp hơn, hay các robots tự tái sinh. Hy vọng đến đây bạn đã được thuyết phục rằng sự tự tái sinh không phải là thứ vô vọng về mặt triết học.
Trong các ví dụ trước, ta chỉ thiết kế các chương trình có chức năng duy nhất là in ra bản thân. Còn bọn “vi khuẩn số” hay viruses còn làm các việc khác nữa. Chuyện này không có gì khó. Giả sử ta có chương trình T làm việc gì đó (phá đĩa cứng chẳng hạn), ta có thể thiết kế chương trình ABT bao gồm đoạn A = P(x), đoạn B giống như trước, và đoạn T. Thay vì A ghi x = B vào vùng nhớ, thì trong trường hợp này A sẽ ghi x = BT, còn B thì in P(x)x như cũ. Như vậy ta đã bổ túc cho chương trình T chức năng tự tái sinh mà không cần suy nghĩ gì nhiều.
Cái mô hình máy có bộ vi xử lý truy cập một vùng nhớ chung của ta chính là mô hình máy Turing. Lần tới ta sẽ dùng ý tưởng về sự tự tái sinh này để chứng minh rằng có bài toán không quyết định được bằng máy Turing (xem thêm chung qui chỉ tại Cantor). Như đã nói trong bài “chung qui chỉ tại Cantor”, khái niệm tự tham chiếu là một khái niệm then chốt. Vì thế, cũng không ngạc nhiên lắm khi sự tự tái sinh có liên quan đến vấn đề không quyết định được.
Bài tập 1: viết một chương trình C chép file X sang file Y, sau đó tự in bản thân nó (C) ra stdout.
Để minh họa khái niệm tự in và tự tham chiếu một lần nữa, hãy xét ví dụ sau (tôi lấy ở đây):
Bài tập 2: viết một chương trình C tìm một câu tiếng Việt có nội dung tương tự.
Bài tập 3: có phải tất cả các ngôn ngữ đều có một câu dạng này hay không? Ngôn ngữ với tính chất gì thì có một câu như vậy?
Ta đã “chứng minh” rằng mọi chương trình có thể lấy được đoạn mã của chính nó. (Chứng minh điều này một cách chặt chẽ bằng toán học cũng không có gì khó khăn, tương tự như các ý tưởng ta đã trình bày, chỉ cần định nghĩa rõ ràng thế nào là một chương trình.) Phát biểu này thường được gọi là định lý đệ qui (recursion theorem).
Đến đây, ta minh họa một ứng dụng của định lý đệ qui. Ta sẽ dùng định lý này để chứng minh rằng bài toán dừng không quyết định được. Tôi sẽ thảo luận ý tưởng chính, bỏ qua các chi tiết toán không quan trọng. Bài toán dừng đại khái có thể phát biểu như sau: “cho một đoạn mã chương trình [M] và một chuỗi w, quyết định xem với input w thì M có dừng sau một số hữu hạn các bước hay không?” (Ta dùng [M] để chỉ đoạn mã của chương trình M.)
Định lý: không tồn tại chương trình nào giải bài toán dừng.
Chứng minh: giả sử có chương trình A giải bài toán dừng, ta thiết kế một chương trình M để làm phản chứng như sau
Như vậy A không thể nào quyết định xem M có dừng hay không!
Chứng minh trên rất đặc trưng cho lập luận đường chéo của Cantor, thường được dùng cho các bài toán mà đối tượng có tính tự tham chiếu. Lý luận kiểu như trên là một trong những công cụ rất mạnh để chứng minh phản chứng trong lý thuyết tính toán.
Không biết các bạn thế nào chứ lúc đầu tôi thấy việc thiết kế chương trình tự in khá là phản trực quan. (Tưởng tượng một nhà máy sản xuất gỗ lại có thể sản xuất một nhà máy sản xuất gỗ y hệt như nó.) Thế mà câu trả lời không những là có các chương trình như vậy, mà còn có cả một nguyên tắc viết các chương trình đó.
Trong bài này, ta đã đi từ một câu hỏi tự tham chiếu này (chương trình tự in) sang câu hỏi tự tham chiếu khác (bài toán dừng). Ta dùng câu trả lời có cho câu hỏi đầu để trả lời không cho câu hỏi thứ hai.