Chatbox

Các bạn vui lòng dùng từ ngữ lịch sự và có văn hóa,sử dụng Tiếng Việt có dấu chuẩn. Chúc các bạn vui vẻ!
21/12/2011 15:12 # 1
sevenrock
Cấp độ: 14 - Kỹ năng: 13

Kinh nghiệm: 139/140 (99%)
Kĩ năng: 78/130 (60%)
Ngày gia nhập: 13/10/2010
Bài gởi: 1049
Được cảm ơn: 858
Nghệ thuật lập trình Lisp


Cùng với sự phát triển của CadViet, sự quan tâm của các member đối với lisp ngày càng tăng. Mọi người đều thấy rất rõ rằng, lisp là phương tiện hữu hiệu để nâng cao năng suất và hiệu quả sử dụng AutoCAD. Là người dẫn chương trình của diễn đàn Lisp & VBA, ssg rất vui khi số lượng, chất lượng cũng như sự nhiệt tình của đội ngũ lập trình viên lisp của CadViet phát triển không ngừng.
Ssg lập topic này như là nơi giao lưu, chia sẻ, trao đổi kinh nghiệm và học hỏi lẫn nhau cho tất cả những ai quan tâm đến “cái gọi là nghệ thuật lập trình Lisp.
Ssg xin mở đầu chuyên mục này bằng một bài phân tích, dựa trên yêu cầu của bạn vinataba vừa mới post trên diễn đàn: Tính và ghi kết quả giá trị trung bình của các đối tượng text.
Một bài toán xem ra rất đơn giản, hầu như ai biết chút ít về lisp cũng có thể làm được. Nhưng đằng sau cái đơn giản ấy có nhiều điều đáng được đưa ra xem xét…
Trước hết là code của ssg:

 
;;;---------------------------------------------------------
(defun aver(L / tot x) ;;;Calc average from list of reals
(setq tot 0.0)
(foreach x L (setq tot (+ tot x)))
(if L (/ tot (length L)))
)
;;;---------------------------------------------------------
(defun wtxt(txt p / sty d h1 h2 wf h) ;;;Write txt on graphic screen at p
(setq
    sty (getvar "textstyle")
    d (tblsearch "style" sty)
    h1 (cdr (assoc 40 d))
    h2 (cdr (assoc 42 d))
    wf (cdr (assoc 41 d))
)
(if (> h1 0) (setq h h1) (setq h h2))
(entmake (list (cons 0 "TEXT") (cons 7 sty) (cons 40 h) (cons 41 wf) (cons 1 txt) (cons 10 p)))
)
;;;---------------------------------------------------------
(defun C:AVE( / ss p L e v)
;;;SELECT OBJECTS
(setq
    ss (ssget '((0 . "TEXT")))
    p (getpoint "\nBase point:")
    L nil
)
;;;FILTER OBJECTS
(while (setq e (ssname ss 0))
    (setq v (cdr (assoc 1 (entget e))))
    (if (= (atof v) (read v)) (setq L (append L (list (atof v)))))
    (ssdel e ss)
)
;;;CALC AND WRITE RESULT
(wtxt (rtos (aver L)) p)
(princ)
)
;;;---------------------------------------------------------



Và tiếp theo là một số ý kiến:

1) Tính cấu trúc của chương trình
Hầu như tất cả các tài liệu hướng dẫn về lập trình đều nhấn rất mạnh điều này. Chương trình được lập với cấu trúc logic, chặt chẽ sẽ tạo điều kiện rất thuận lợi cho chính bản thân người lập trình: dễ viết, dễ kiểm lỗi, dễ có cái nhìn xuyên suốt toàn bộ chương trình, dễ kế thừa, vận dụng kết quả đạt được cho các chương trình khác, v.v… và v.v…
Ssg không phân tích thêm, nhưng có một sự so sánh đơn giản: viết chương trình có lẽ cũng giống như viết văn! Mình chẳng phải nhà văn nhưng ngày xưa đi học, làm bài luận văn vẫn thường theo các bước:
- Đọc và phân tích kỹ đề bài
- Lập dàn ý bằng các gạch đầu dòng
- Phát triển từng cái gạch đầu dòng, thêm… mắm muối gia vị vào là thành bài văn!
Kết quả cho thấy, bài nào mình càng chịu khó lập kỹ dàn ý thì càng được điểm cao.
Với đề bài này, căn cứ vào công việc phải làm của chương trình, dàn ý được lập như sau (thể hiện rất rõ trong chương trình chính C:AVE):
- Chọn đối tượng: dùng ssget. Thực chất cũng đã kết hợp “chọn” và “lọc” nhưng là “lọc thô”, vì hàm ssget không có tùy chọn nào cho phép phân biệt được text chứa số và text chứa chữ.
- Lọc đối tượng: loại ra các text có chứa chữ. Có nhiều cách để đạt được mục đích này. Ở đây dựa vào hàm read, một đối tượng text không chứa chữ thì kết quả hàm read và hàm atof bằng nhau.
- Tính giá trị trung bình: sau khi đã chọn và lọc, kết quả ta thu được chắc chắn là một list chỉ chứa toàn các số thực. Hàm aver không phải “bận tâm” đến việc check dữ liệu nữa, nó chỉ làm công việc đơn giản là gán các biểu thức toán học thuần túy.
- Ghi kết quả: có vẻ như là công việc dễ nhất, nhưng có một chút rắc rối, sẽ phân tích kỹ hơn ở mục 3.

2) Sự tích lũy tài nguyên
Chương trình được lập ra trước tiên là để đáp ứng một yêu cầu cụ thể (người nêu yêu cầu có lẽ sẽ hài lòng với kết quả này). Nhưng ngoài điều đó ra, chúng ta còn thu được những gì khác nữa hay không? Cái quan trọng nhất đối với lập trình viên chính là các public functions, mà cụ thể ở đây là 2 hàm aver và wtxt. Chúng có thể được dùng trong nhiều các chương trình khác sau này.
Để các hàm này thật sự trở thành public, có phạm vi áp dụng rộng rãi, có 2 điểm cần chú ý khi xây dựng chúng:
- Có tính độc lập cao, không phụ thuộc vào các tùy chọn của người dùng cũng như các điều kiện ràng buộc khác. Ví dụ, hàm aver trên không liên quan gì đến các điều kiện chọn và lọc đối tượng, list L mặc nhiên xem như chỉ chứa toàn các số thực.
- Các hàm có đối số (argument) thường có phạm vi áp dụng rộng hơn hàm không có đối số.
Nếu chúng ta có ý thức về điều này và luôn luôn có định hướng trong khi lập trình thì số lượng public functions tích lũy được sẽ ngày càng nhiều. Đây chính là nguồn tài nguyên, là “vốn liếng” quý giá của lập trình viên. Khi lập một chương trình mới, nếu cần chỉ copy y chang hoặc chỉnh sửa chút ít. Giá trị sử dụng của chúng càng cao hơn khi ta thiết lập được một chương trình lớn, một hệ thống lisp tương đối hoàn chỉnh. Toàn bộ các public functions trên được chuẩn hóa và cho autoload, khi cần thì gọi, đơn giản và tự nhiên như khi bạn gọi các hàm có sẵn của chính AutoLisp (setq, car, cadr, atof, itoa, if, while…)

3) Ghi text ra màn hình graphic
Như đã nói trên, có một rắc rối nhỏ, nhưng nếu không chú ý, nó sẽ thành “lớn chuyện”. Một chương trình rất “hoành tráng” có thể bị exit ngang xương vì một lỗi không đáng có.
Bạn hãy thử vào Text Style, đặt giá trị Height = 0.0000, sau đó thực hiện lệnh text:
Command: text
Specify start point of text or [Justify/Style]: Pickpoint
Specify height <2.5000>: Enter
Specify rotation angle of text <0>: Enter
Enter text: abcd - Enter
Enter text: Enter
Biểu thức lisp tương ứng là:
(command “text” (getpoint) 2.5 0 “abcd”)

Tiếp theo, vào lại Text Style, đặt Height khác 0, thử lại lệnh text:
Command: text
Specify start point of text or [Justify/Style]: Pickpoint
Specify rotation angle of text <0>: Enter
Enter text: abcd - Enter
Enter text: Enter

Điểm khác nhau giữa 2 lần là gì? Lần sau không yêu cầu Specify height nữa. Biểu thức lisp nếu viết như trên bị thừa 1 đối số, AutoCAD sẽ tạo ra text có góc xoay 2,5 độ và nội dung là “0”! Đối số cuối cùng “abcd” được hiểu là “hãy thực hiện lệnh abcd”! Kết quả:
Command: abcd Unknown command "ABCD". Press F1 for help.
Và tiếp theo tất nhiên là exit. Toàn bộ các câu lệnh sau đó sẽ không được thực hiện.
Thật đáng tiếc, khá nhiều người trong chúng ta đã mắc lỗi này (ngay cả chính bản thân ssg trước đây!).
Trên thực tế, người dùng có thể chọn 1 trong 2 dạng trên, tùy theo thói quen sử dụng. Chương trình phải tùy biến theo từng trường hợp cụ thể. Lỗi là do không để ý, đã biết rồi thì có rất nhiều cách tránh. Và nó đã “lôi thôi” như vậy thì tốt hơn hết là xây dựng luôn thao tác ghi text ra màn hình graphic thành 1 public function. Hàm wtxt trên là một hướng khả dĩ. Ta chỉ cần gọi nó với 2 đối số: txt (nội dung text) và p (điểm chèn), nó sẽ thực hiện các thao tác y như người dùng gọi lệnh text (với Text Style, Text Height và Width Factor theo các giá trị hiện hành)

4) Dữ liệu kiểu list
Lisp là gì? Câu này đã có nhiều người hỏi và nhiều người trả lời. Với lập trình viên, chỉ đơn giản là LISt Processing – Xử lý danh sách. Do đó, kiểu dữ liệu list (List Data Type) mặc nhiên là kiểu dữ liệu quan trọng nhất của ngôn ngữ lisp. AutoLisp cung cấp rất nhiều functions để thao tác với list. Mọi nguồn thông tin và dữ liệu phức tạp, bạn hãy cố gắng convert chúng về dữ liệu kiểu list, vấn đề sẽ đơn giản đi rất nhiều.
Trong ví dụ trên, ta hoàn toàn có thể xây dựng hàm aver với đối số là Selection Set (SS). Nhưng khi đó, bạn sẽ không dùng được hàm foreach rất gọn gàng như trên mà phải dùng repeat hoặc while, và chắc chắn code sẽ dài hơn. Đó là nói cụ thể trong bài này, còn trong nhiều trường hợp khác, rất nhiều kiểu dữ liệu sẽ dễ dàng chuyển sang list nhưng không thể chuyển được thành SS. Điều đó có nghĩa là hàm aver được xây dựng với đối số list sẽ có phạm vi áp dụng rộng rãi hơn là dùng đối số SS.
Tóm lại, muốn làm chủ Lisp, bạn phải biết cách làm chủ List Data Type!

5) Command và Entmake
Trong hàm wtxt trên, ta có thể dùng 2 cách ghi text: dùng entmake hoặc gọi command, kết quả cuối cùng là như nhau. Nhưng tại sao lại là entmake mà không là command? Lý do trong trường hợp này thì đã rõ, gọi command cho text vướng phải “vấn đề rắc rối” đã nêu (tất nhiên là vẫn xử lý được nhưng dài dòng hơn). Còn nói chung, mỗi cách đều có ưu và nhược điểm.
Cụ thể, hàm Command có các ưu điểm sau:
- Đơn giản, dễ hiểu, dễ nhớ. Gọi lệnh AutoCAD thế nào thì dùng command như vậy.
- Sau khi thực hiện command, AutoCAD trả về điểm đồ họa cuối cùng, được lưu trong biến “lastpoint”. Đây là điểm tham chiếu quan trọng cho chương trình khi cần thực hiện nhiều thao tác liên tiếp trên màn hình graphic.
Ví dụ, đoạn code sau vẽ liên tiếp 10 bậc thang từ điểm chuẩn p. Entmake không làm thay đổi “lastpoint” nên không dùng được như vậy:
(setq p (getpoint))
(repeat 10
(command "line" p (list (car p) (+ (cadr p) 20))
(list (+ (car p) 25) (+ (cadr p) 20)) "")
(setq p (getvar "lastpoint")))
)
Nhược điểm:
- Hàm command bị ảnh hưởng của biến osmode. Phải save os, disable os, rồi reset os (lôi thôi quá!)
- Tốc độ thực hiện hàm command chậm hơn entmake. Điều này đặc biệt quan trọng khi khối lượng thao tác khá nhiều.
Ưu điểm của command chính là nhược điểm của entmake và ngược lại. Tùy trường hợp cụ thể mà lựa chọn sử dụng.

6) Return value
Ngôn ngữ lisp có một ưu điểm tuyệt vời: mọi biểu thức lisp đều hoàn trả (return) một giá trị nào đó. Giá trị ấy có thể thuộc những kiểu dữ liệu khác nhau, nhưng về mặt logic đều được hiểu là T (true). Nếu không, nó hoàn trả nil (được hiểu là false). Điều này có lẽ hơi khó hiểu với người mới tiếp cận lisp. Nhưng khi đã quen, bạn sẽ thấy rất “khoái” vì nó giúp cho code của bạn ngắn gọn hơn rất nhiều.
Ví dụ 1, kết thúc hàm aver, thay vì theo logic thông thường, bạn phải viết là:
(if (/= L nil)
(setq divi (/ tot (length L)))
(setq divi nil)
)
(setq result divi)
Nhưng chỉ cần 1 dòng đơn giản:
(if L (/ tot (length L)))
Giá trị hoàn trả của phép chia (/ tot (length L)) cũng là giá trị hoàn trả của hàm if, và cũng là giá trị hoàn trả của cả function aver.

Ví dụ 2, đoạn Filter Objects ở trên, thay vì phải viết:
(setq OK T)
(while (= OK T)
(setq e (ssname ss 0))
(if (/= e nil)
(progn
(setq v (cdr (assoc 1 (entget e))))
(if (= (atof v) (read v)) (setq L (append L (list (atof v)))))
(ssdel e ss)
)
(setq OK nil)
)
)

Bạn chỉ cần đơn giản:
(while (setq e (ssname ss 0))
(setq v (cdr (assoc 1 (entget e))))
(if (= (atof v) (read v)) (setq L (append L (list (atof v)))))
(ssdel e ss)
)
Biểu thức (setq e (ssname ss 0)) có vai trò “đa nhiệm”, vừa thực hiện phép gán, vừa là biểu thức điều kiện cho hàm while, vừa tạo “rào cản” ngay từ đầu, không cần phải rào thêm (if (/= e nil)… bên trong nữa. Một công đôi ba việc, đúng là tuyệt vời!

Viết lan man rồi cũng phải đến hồi kết, nếu không sẽ bị cho là… lạc đề.
Lập trình là nghệ thuật – lập trình viên là nghệ sĩ! Đã là nghệ sĩ, ai cũng có cái “máu” ngang tàng, và chương trình mỗi người lập ra sẽ mang đậm dấu ấn cá nhân. Thế nhưng, mỗi người chúng ta đã đến với CadViet và giao lưu với nhau, dù muốn hay không cũng có sự ảnh hưởng qua lại lẫn nhau. Bản thân ssg, từ khi tham gia CadViet cũng đã học hỏi được nhiều điều. Đến giờ, trong các chương trình mình lập ra, không thể phân biệt được cái nào là kiến thức “kinh điển”, cái nào tự sáng tác, cái nào học được của anh Hoành, cái nào chịu ảnh hưởng của vndes… Tất cả đã kết hợp lại theo một phong cách mới.
Một ngày đẹp trời nào đó, bạn lang thang trên mạng và sưu tầm được một đoạn lisp. Nhìn vào cung cách coding, có thể bạn sẽ nhận ra ngay đây là phong cách của anh em nhà mình – phong cách lập trình CadViet!

Kết thúc bài này, ssg có một đề bài… thách đấu:
Lập lại public function (defun wtxt(txt p)…) ở trên thành dạng tối giản. Muốn viết kiểu gì cũng được nhưng phải bảo đảm có chức năng y chang như trên. Tiêu chí đánh giá duy nhất là kích thước bytes càng nhỏ càng tốt. Thống nhất giữ nguyên function name là wtxt (đừng giản lược đến mức chỉ còn có 1 chữ w!).
Nào, xin mời các nghệ sĩ lập trình CadViet!
Nguồn:http://www.cadviet.com


Nguyễn Vĩnh Trọng-K16DCD3
Smod Góc Học Tập
Yahoo:trong_nguyen15
Phone:0905360491

Punish is my wish
destroy is my will

 
Copyright© Đại học Duy Tân 2010 - 2024