Trong bài trước, tôi đã viết về cách lập trình không sử dụng IF sẽ có thể giúp bạn khám phá ra các giải pháp tốt hơn. Trong bài này, tôi sẽ tiếp tục viết về thử thách lập trình mà không dùng vòng lặp. Vòng lặp ở đây có nghĩa là for
, for...in
, for...of
, while
, and do...while
. Tất cả đều tương tự nhau.
Imperative vs Declarative
Đây là những khái niệm lớn. Cách đơn giản nhất để giải thích về chúng là:
- Imperative đại diện cho HOW
- Declarative đại diện cho WHAT
What? How? Và tại sao chúng ta nên quan tâm đến nó?
Cách tiếp cận imperative đại diện cho một list các bước. Làm cái này trước, rồi làm cái kia sau, và sau cùng làm một cái khác. Ví dụ: Duyệt qua một danh sách các con số từng cái một rồi cộng giá trị của nó vào một biến tổng
Cách tiếp cận declarative đại diện cho cái gì chúng ta có và chúng ta cần cái gì. Ví dụ: Chúng ta có một danh sách các con số và chúng ta cần tổng giá trị của chúng
Ngôn ngữ của imperative gần giống với ngôn ngữ của máy tính ngày nay bởi vì chúng chỉ biết cách làm thế nào để thực thi theo cấu trúc. Còn ngôn ngữ của declarative gần giống với cách chúng ta nghĩ và ra lệnh.
Tin tốt lành là ngôn ngữ máy tính vẫn cho phép chúng ta declarative để thực thi imperative! Bài viết này tập trung vào việc thay thế declarative cho vòng lặp imperative.
Tính toàn vẹn
Trành dùng vòng lặp không chỉ nằm ở việc áp dụng declarative. Nó còn giúp toàn vẹn dữ liệu
Tính toàn vẹn dữ liệu lại là một chủ đề lớn khác nữa, nhưng tổng thể là không được modify data trong các biến và instance properties để trình diễn trạng thái của ứng dụng. Thay vào đó, trạng thái được lưu trữ trong các giai đoạn gọi các function. Các function gọi lẫn nhau một cách tuần tự để phát triển điểm bắt đầu ban đầu sang các dạng dữ liệu khác. Không có biến nào bị biến đổi khi chạy
Thay vì lạm dụng trạng thái để thực hiện các hoạt động đơn giản, giữ cho toàn vẹn sẽ an toàn hơn nhiều và cũng clean hơn. Tuy nhiên, lợi ích lớn của toàn vẹn chính là cách nó làm cho code dễ maintain và mở rộng hơn. Ví dụ, khi quản lý bất kỳ một trạng thái ứng dụng nào đó theo cách toàn vẹn, chúng ta có thể kiểm tra tình trạng bất kỳ lúc nào, undo lại sự thay đổi trạng thái, hoặc kể cả trở về quá khứ của trạng thái trước đó để kiểm tra ứng dụng lúc đó. Khi trạng thái không thay đổi, chúng ta luôn làm cho ứng dụng phải nhớ các giai đoạn phát triển của nó
Code dễ hiểu và performance có thể bị ảnh hường khi chúng ta lập trình theo cách toàn vẹn. Tuy nhiên, có nhiều chiến lược để có thể có cả 2 lợi ích là toàn vẹn và vừa performace tốt, code vừa dễ hiểu. Nhưng tôi sẽ viết nó trong tương lai.
Đệ Quy
Một cách khác để tránh vòng lặp imperative là thông qua đệ quy.
Đệ quy rất đơn giản. Làm một function gọi chính nó(tạo nên 1 vòng lặp) và thiết kế một điều kiện exit ra khỏi vòng lặp. Tôi không chắc đệ quy có thể được xếp vào loại declarative, nhưng nó chắc chắn là một sự thay thế cho việc sử dụng vòng lặp imperative. Tuy nhiên, hãy cẩn thận khi sử dụng đệ quy vì nó không hoạt động như là một vòng lặp thông thường. Tôi cũng không nghĩ rằng đệ quy sẽ giúp code của bạn dễ đọc trong đa số trường hợp.
Đôi khi, đệ quy là cách dễ nhất để hoàn thành thử thách này. Không có đệ quy, chúng ta sẽ cần maintain và sử dụng cấu trúc Stack của chúng ta(nhưng cũng không quá khó)
Trong mọi trường hợp, sẽ rất vui hoàn thành thử thách này mà không sử dụng bất kỳ vòng lặp imperative nào!
Đây là vài ví dụ về sử dụng-không sử dụng vòng lặp. Tất cả các ví dụ đều bằng Javascript. Nói cho tôi biết bạn nghiêng về cái nào hoặc bạn nghĩ cái nào dễ đọc hơn.
Thử thách #1: Tính tổng của một list các số
Ví dụ:
1 2 3 | const arrayOfNumbers = [17, -4, 3.2, 8.9, -1.3, 0, Math.PI]; |
Đây là cách dùng vòng lặp:
1 2 3 4 5 6 7 | let sum = 0; arrayOfNumbers.forEach((number) => { sum += number; }); console.log(sum); |
Đây là cách không dùng vòng lặp:
1 2 3 4 5 6 | const sum = arrayOfNumbers.reduce((acc, number) => acc + number ); console.log(sum); |
Không có gì bị biến đổi trong cách không dùng vòng lặp. Thay vì vậy, chúng ta gọi một mớ hàm callback và trạng thái đã được giữ lại giữa các lần gọi này cho đến khi ra trạng thái của sum
Còn đây là cách dùng đệ quy:
1 2 3 4 5 6 7 8 9 | const sum = ([number, ...rest]) => { if (rest.length === 0) { return number; } return number + sum(rest); }; console.log(sum(arrayOfNumbers)) |
Hàm sum
gọi nó và cứ mỗi lần hoàn thành nó sẽ sữ dụng rest operator để giảm array xuống. Và nó sẽ dừng khi array rỗng. Trong khi bạn khĩ đây là cách thông minh, tôi lại nghĩ nó không dễ đọc bằng cách đơn giản là gọi reduce
.
Thử thách #2: Tạo thành một câu từ hỗn hợp nhiều nội dung
Có một array các chuỗi và khác nhau về type, hãy kết hợp tất cả các chuỗi và bỏ qua các type khác
Ví dụ:
1 2 3 | const dataArray = [0, 'H', {}, 'e', Math.PI, 'l', 'l', 2/9, 'o!']; |
Output được mong đợi là “Hello!”
Bạn có thể chỉ đơn giản là dùng typeof
để check giá trị và thể loại của string
.
Đây là giải pháp sử dụng vòng lặp đơn giản
1 2 3 4 5 6 7 8 9 10 | let string = '', i = 0; while (dataArray[i] !== undefined) { if (typeof dataArray[i] === 'string') { string += dataArray[i]; } i += 1; } console.log(string); |
Đây là cách không dùng vòng lặp:
1 2 3 4 5 | const string = dataArray.filter(e => typeof e === 'string') .join(''); console.log(string); |
Thử thách #3: Chuyển đổi giá trị vào record
Let’s say we have an array of book titles and we need to convert every title into an object and give that object a unique ID.
Có một mảng tiêu đề các quyển sách, hãy chuyển đổi mỗi tiêu đề vào 1 object và gửi mỗi object đó 1 unique ID
Ví dụ:
1 2 3 4 5 6 7 8 9 10 11 12 13 | const booksArray = [ 'Clean Code', 'Code Complete', 'Introduction to Algorithms', ]; // Desired output newArray = [ { id: 1, title: 'Clean Code' }, { id: 2, title: 'Code Complete' }, { id: 3, title: 'Introduction to Algorithms' }, ]; |
Đây là giải pháp dùng vòng lặp:
1 2 3 4 5 6 7 8 9 10 11 12 | const newArray = []; let counter = 1; for (let title of booksArray) { newArray.push({ id: counter, title, }); counter += 1; } console.log(newArray); |
Đây là giải pháp không dùng vòng lặp:
1 2 3 4 5 6 7 | const newArray = booksArray.map((title, index) => ({ id: index + 1, title })); console.log(newArray); |
Mấy cái method map
/filter
/reduce
là những thứ mà tôi yêu thích áp dụng nó khi xử lý array. Bạn có thể dùng chúng để làm rất nhiều thứ hay ho. Chú ý theo dõi các bài sau về chúng. Bây giờ tôi chỉ để lại một tấm ảnh mà không nói gì (Credit to Steven Luscher)
TechTalk via Tourist Đệ Quy