Tìm hiểu ngôn ngữ bằng các ví dụ thực hành
Các hàm, các dãy s và các phép bao hàm
Bài toán đầu tiên của Dự án Euler mà bạn sẽ giải quyết là Bài toán 6 (xem phần i nguyên),
trong đó yêu cầu bạn tính toán tổng các bình phương của các số tự nhiên đầu tiên, bình phương
của tổng các số đó và sau đó đưa ra hiệu của chúng. Với bài toán này, bạn sẽ sử dụng Vòng lp-
Đọc-Tính toán-In ra (REPL - Read-Evaluate-Print-Loop) của CoffeeScript. Lit kê 1 hiển thị mã
kết quả đầu ra tương ứng từ phiên làm việc của REPL.
Liệt kê 1. Bài toán 6 từ REPL
coffee> square = (x) -> x*x
[Function]
coffee> sum = (nums) -> nums.reduce (a,b) -> a+b
[Function]
coffee> diff = (nums) -> (square sum nums) - (sum nums.map square)
[Function]
coffee> diff [1..100]
25164150
Giải thích chi tiết:
1. Trong REPL, ta định nghĩa một hàm. (Như đã đề cập trong Phần 1, CoffeeScript đề cao
bản chất lập trình hàm của JavaScript, loại bỏ nhiều cú pháp giống C của JavaScript đã
làm cho cú pháp CoffeeScript trở nên dễ nhìn và dlập trơ hơn).
Ví dụ trong Liệt kê 1 định nghĩa một hàm tên là square (bình phương) và i rằng
nhận một tham số duy nhất và trả về tích của phép nhân tham số đó với chính nó (bình
phương nó lên). Rồi REPL cho bạn biết rằng bạn đã định nghĩa một hàm.
2. Định nghĩa một hàm khác tên là sum (tng số) nhận một tham số duy nhất: một mảng.
Sau đó hàm Sum gi phương thức reduce (thu gn) trên mảng đó.
Phương thức reduce là hàmsẵn của JavaScript (được thêm vào trong JavaScript 1.8).
Phương thức reduce tương tự như hàm reduce trong Python hoặc hàm fold (gấp) trong
Haskell hoặc Scala. Nó lấy một hàm và di chuyn từ trái sang phải trên mảng đó, áp dụng
hàm cho giá tr được trả về trong lần tính trước đó và giá tr tiếp theo trong mảng. Cú
pháp hàmđọng của CoffeeScript làm cho phương thức reduce dễ sử dụng. Trong
trường hợp này, hàm được chuyển sang hàm reduce được quy định là (a,b) -> a + b.
Đây là một hàm nhận hai giá trị và cộng chúng với nhau, do đó làm cho tất cả các phần tử
của mảng được cộng với nhau.
3. Tạo một hàm tên là diff (hiu), nó lấy một mảng các số, tính toán hai biểu thức con, rồi
trừ chúng cho nhau. Biểu thức con đầu tiên chuyển mảng tới hàm sum, ri lấy kết quả và
chuyển nó tới hàm square.
CoffeeScript cho phép bạn bỏ qua các dấu ngoặc đơn trong nhiều trường hợp vì thế
không có sai sót nào. Ví dụ, square sum nums tương đương với square(sum(nums)).
Biểu thức con thứ hai gọi phương thức map (ánh xạ) của mảng, đó là một phương thức
JavaScript 1.8 khác nhận một hàm khác làm đầu vào của nó. Sau đó nó áp dụng hàm đó
cho từng thành viên của mảng, tạo ra một mảng mới từ các kết quả. Ví dụ trong Liệt kê 1
sử dụng hàm bình phương làm tham số đầu vào cho hàm map, cung cấp cho bạn một
mảng có các phần tử của mảng là các s bình phương của mảng đầu vào. Sau đó, bạn chỉ
cần chuyển mảng này ti hàm sum để nhận được tng của các số bình phương.
4. Chuyển mảng các số thích hợp vào hàm diff để giải Bài toán 6, khi sử dụng một dãy s
[1..100].
Dãy s này tương đương với một mảng của tất cả các số từ 1 đến 100, bao gm cả số
100. Nếu bạn đã muốn dải số này không bao gm cả số 100 bạn phải viết [1...100], tức
là dùng ba dấu chấm thay vì chỉ có hai. Việc chuyển mảng này vào hàm diff cung cấp
cho bạn lời giải cho Bài toán 6.
Hãy quay trlại một chút và xem xét Bài toán 1 của Dự án Euler (xem phần i nguyên), trong
đó yêu cầu bạn tính tổng tất cả các số nguyên nhỏ hơn 1000 có thể chia hết cho 3 hoặc 5. Bạn có
th nghĩ rằng đây slà bài toán đơn giản nhất trong số các bài toán của Dự án Euler. Bạn thể
dễ dàng giải nó chỉ bằng cách sử dụng các hàm và các dãy số, như trong Bài toán 6. Tuy nhiên,
với tính năng phép bao hàm của CoffeeScript, bạn thể đưa ra lời giải ngắn gọn trong Lit kê 2.
Liệt kê 2. Giải Bài toán 1 bằng cách sử dụng các phép bao hàm (comprehensions)
coffee> (n for n in [1..999] when n % 3
== 0 or n % 5 == 0).reduce (x,y) -> x+y
233168
Thật dễ chịu khi có thể giải một bài toán ch bằng mt dòng mã duy nhất và cú pháp cô đọng của
CoffeeScript cho phép nó trở thành ngôn ngmt dòng lệnh. Lời giải trong Liệt kê 2 sẽ tạo mt
danh sách tất cả các số nguyên là bội số của 3 hoặc 5 bằng cách sử dụng mt phép bao hàm.
Danh sách được tạo ra bằng cách bắt đầu với dãy s[1..999] nhưng chỉ sử dụng các giá tr chia
hết cho 3 hoặc 5. Sau đó sử dụng mt hàm reduce khác để tính tổng các giá trị đó. REPL tính
toán một dòng mã này và in ra lời giải cho bài toán đó.
Phần tiếp theo, chúng ta sẽ gii các bài toán phức tạp hơn một chút và tiếp tục khám p
CoffeeScript.
Về đầu trang
Các câu lnh khối, các mảng và các tệp
Bài toán 4 của Dự án Euler (xem phần i nguyên) yêu cầu bạn tìm ra s lớn nhất có các chữ số
có thể đc xuôi hay đọc ngược đều được (số palindrome), số này phải là tích của hai số ba
chữ số. Có rất nhiều cách để giải quyết vấn đề này; Liệt kê 3 hin thị mt cách gii.
Liệt kê 3. Thử nghiệm với các palindrome
isPal = (n) ->
digits = []
while n > 0
digits.push(n % 10)
n = Math.floor(n / 10)
len = digits.length
comps = [0..len/2].map (i) -> digits[i] == digits[len-i-1]
comps.reduce (a,b)-> a && b
vals = []
vals.push(x*y) for x in [100..999] for y in [100..999]
pals = vals.filter (n) -> isPal(n)
biggest = pals.reduce (a,b) -> Math.max(a,b)
console.log biggest
1. Định nghĩa một hàm tên là isPal, mà nó sẽ kiểm tra xem mt số có là một palindrome
hay không. Hàm này phức tạp hơn một chút so với những hàm trước đây.có bảy dòng
mã trong phn thân của mình.
Bạn có thể nhận thấy rằng CoffeeScript không sử dụng dấu ngoặc nhọn ({ }) hoặc bất kỳ
cơ chế tường minh nào khác để biểu thị điểm bắt đầu và kết thúc của hàm. Nó sử dụng
khoảng trống (thụt đầu dòng), giống như Python và bắt đầu với cùng một pháp để biểu
th một hàm: danh sách tham s, tiếp sau bằng mũi tên ln hơn (->). Sau đó, bạn tạo ra
mt mảng rỗng cho các chữ số của các số và bắt đầu một vòng lặp while. Vòng lặp này
tương tự như vòng lặp while của JavaScript (hoặc C, Java và v.v). Điều kin n > 0
không đòi hi các dấu ngoặc đơn xung quanh nó. Phần thân của vòng lặp được thụt vào
sâu hơn nữa để cho biết nó là mt phần của vòng lặp. Bên trong vòng lặp, bạn cắt rời chữ
scui của số đó, đẩy nó lên phía trước của mng, rồi chia số đó cho 10, loi bỏ phần dư
của phép chia. Việc này dn đến mt mng các chữ số của số ban đầu. Bạn cũng có thể
thay thế vòng lặp đơn giản bằng digits = new String n, biến đổi n thành một chuỗi.
Phần còn li của mã này hoạt động như nó vốn có.
2. Sau khi bạn có mảng các chữ số, hãy tạo ra một mảng bng mt nửa chiều dài của mảng
này. Sử dụng hàm map để lần lượt chuyển từng phần tcủa mng này thành mt giá trị
Boolean tương ứng để xem các chữ số cách điểm bắt đầu và kết thúc của mảng có bằng
nhau không.
Nếu tất cả các giá tr Boolean đều là đúng, thì bạn có một palindrome. Để kim tra điều
này, chỉ cần sử dụng mt hàm reduce khác, ln này m thực hiện phép toán logic AND
các giá tr Boolean với nhau.
3. y gibạn đã định nghĩa hàm isPal, hãy sdụng nó để kim tra các palindrome. Hãy
kiểm tra tất cả các số là tích của hai số có ba chữ số.
a. Tạo hai phép bao hàm, mi phép bao hàm tri rộng từ số có ba chsố nh nhất
(100) đến số có ba chsố lớn nhất (999).
b. Đối với mi phép bao hàm, lấy tích số và đặt nó vào trong mt mng.
c. Sử dụng mt hàm reduce khác để tìm phần tử ln nhất trong mảng. Cuối cùng,
phần tnày được in ra bằng cách sử dụng console.log.
d. Lưu phần tử này vào một tệp.
Liệt kê 4 Liệt kê 4 cho thấy cách thực hiện và tính thời gian giải.
Liệt kê 4. Thực hiện lời giải cho Bài toán 4
$ time coffee p4.coffee
906609
real 0m2.132s
user 0m2.106s
sys 0m0.022s
Kịch bản lệnh trong Liệt kê 4 phải mất hơn hai giây để thực hiện trên một máy tính chạy nhanh,
phần lớn là do phép bao hàm ghép để tạo ra 899*899 = 808.201 giá trị để kiểm tra (nhiều giá tr
trong đó bị lặp lại). Bạn hãy thử tối ưu hóa mã trong Liệt kê 3. (Gợi ý: Chuyển đổi số thành mt
chui ký tự thực sự nhanh hơn nhiều).
Bài toán 22 của Dự án Euler (xem phần i nguyên) yêu cầu bạn phải thực hiện mt phép tính
phức tạp trên mt danh sách các chuỗi ký tự. Bạn sẽ đọc danh sách từ một tệp, phân tích cú pháp
các ni dung trong mt danh ch, sắp xếp nó và chuyển đổi mi chuỗi ký tự thành mt số, rồi
tính tng các tích của mi số nhân với vị trí của nó trong danh sách. Bài toán 22 cho phép bạn
xem các tệp làm việc ra sao trong CoffeeScript. Ngoài ra còn có mt số thao tác chuỗi và nhiều
th thuật mng hơn nữa. Lit kê 5 hin thị lời giải.
Liệt kê 5. Làm việc với các tệp trong CoffeeScript
path = "/path/on/your/computer/names.txt"
fs = require "fs"
contents = fs.readFileSync path, "utf8"
strs = contents.split(",")
names = strs.map (str) -> str[1 .. str.length - 2]
names = names.sort()
vals = names.map (name) -> name.split("")
vals = vals.map (list) ->
list.map (ch) -> 1 + ch.charCodeAt(0) - 'A'.charCodeAt(0)
vals = vals.map (list) ->
list.reduce (a,b) -> a+b
vals = ((i+1)*vals[i] for i in [0...vals.length])
total = vals.reduce (a,b) -> a+b
console.log total
1. Lưu tệp này. Có một liên kết trong phần mô tả bài toán hoặc bạn có thể sử dụng mã
nguồn kèm theo bài viết này. y thay đổi giá trị của biến đường dẫn thành đường dẫn
tới tệp trên máy tính của bạn.
CoffeeScript không kèm theo bất kỳ thư viện đặc biệt nào để làm việc với các tệp. Thay
o đó, nó dùng node.js và mô đun fs của mình. Nạp mô đun fs bằng cách sử dụng hàm
require.
2. Đọc các nội dung của tệp này bằng cách sử dụng fs.readFileSync. Tệp này trông ging
như "MARY", "PATRICIA" và v.v. khởi đầu mt chuỗi duy nhất, vì thế hãy sử dụng
phương thức split để phân chia nó thành từng phần theo các dấu phẩy.
Mỗi chuỗi ký tự vẫn còn có dấu nháy kép (") ở đầu và cuối của chuỗi. Để loi b chúng,
hãy sử dụng hàm map thay thế mi chuỗi bằng mt chuỗi nhỏ hơn. Nếu str là một
chui, thì str[1 .. str.length -2] là một chuỗi con bắt đầu bằng ký tự thứ hai và kết
thúc bằng ký tự tiếp theo đến ký tự cui cùng. Mã này sẽ loại bỏ chính xác các ký tự đầu
cui—chính là các dấu nháy kép kchịu đó. Việc cắt các chuỗi ra thành các chui
nhỏ hơn có thể rất thuận tin.
3. Sau khi bạn có một danh sách các chuỗi không có dấu nháy kép, bạn đã sn sàng sắp xếp.
Hãy sử dụng phương thức sort (sắp xếp) của mảng. Bạn cần chuyển đổi các chuỗi ký t
tnh một s ở đây mỗi ký tsẽ phù hợp với vị trí của nó trong bảng chcái (A -> 1, B -
> 2, C -> 3 và v.v).
a. Chuyển từng chuỗi thành mt mảng các ký tự bằng cách sử dụng lại phương thức
split.
b. Ánh xạ mi ký tự tới mt giá trị số bằng cách sử dụng phương thức charCodeAt.
c. Tính tng các giá trị số này bằng cách sử dụng một phép toán reduce.
Danh sách các chui ký tđược chuyển đổi thành mt danh sách các số.
4. Nhân mi số với vị trí của nó trong danh sách và cng chúng li với nhau bằng cách s
dụng phép bao hàm khác. Tạo một mảng mới trong đó mi phần tử được tạo ra bằng cách
nhân mt phần tử của mảng trước đó với vị trí của nó. Tính tng các phần tử của mng
này bằng cách sử dụng thêm một phép toán reduce nữa và in ra tng số.
Một ln nữa, bạn có thể lưu công việc này vào một tệp rồi thực hiện và tính thời gian chạy nó,
như thể hiện trong Liệt kê 6: