Sử dụng select, reject, detect, collect hay inject

Hôm rồi làm task sau đẩy pull, mình có gặp một comment là thay vì dùng map thì chuyển sang dùng inject. Lúc ấy, cũng không rõ tại sao lại phải dùng thay thế như vậy. Vì thực sự khi làm mình rất ít khi dùng inject. Hồi sau mò lên bác google search các kiểu xem nó khác nhau như nào, tại sao phải dùng thằng này mà không phải thằng kia. Từ đó, nảy ra cái ý nghĩ muốn tìm hiểu về những method thao tác vòng lặp trong Ruby xem chúng nó tương giống và khác nhau như nào. Lúc nào thì nên ưu tiên sử dụng thằng nào hơn.

Loop trong Ruby có vẻ như là một bước tiến lớn so với các ngôn ngữ lập trình khác. Ruby có khá nhiều những method thao tắc vòng lặp với Array như: map, collect, select, reject, inject hay detect... và có lẽ các bạn đã từng rất quen thuộc, quen mắt hay quen dùng một trong những thằng này. Ở bài viết này mình sẽ chia sẻ về việc tìm hiểu những tương đồng và khác biệt, rồi ưu ái khi nào dùng những thằng này.

Ví dụ: Tôi có mảng a = [1, 2, 3, 4, 5, 6]

(*) = map, collect, select, reject, inject hay detect
a.(*) { |n| n > 3 }
=> Các gía trị trả về khi sử dụng với từng thằng (*)  như sau: 
map & collect:  [false, false, false, true, true, true]
select: [4, 5, 6]
reject: [1, 2, 3]
detect: 4
inject: sinh ra errors: undefined method > for false:FalseClass.

Từ cái ví dụ đơn thuần kia, ta cũng nhận thấy: mapcollect có kết qủa như nhau hẳn rồi 2 thằng này về cơ bản sẽ giống nhau. Thằng reject và thằng select thì như đối thủ của nhau, hai thằng là hai nửa đối ngịch nhau. Và thằng detect hay thằng inject thì là 2 thằng ngoại đạo.

Từ đây, ta sẽ đi tìm hiểu rõ từng thằng rồi từ đó thêm hiểu hơn về sự khác nhau và biết là khi nào nên áp dụng từng thằng chúng nó.

  1. MAP

The map method takes an enumerable object and a block, and runs the block for each element

  1. COLLECT Ta thường dùng khi muốn tạo ra một danh sách các phần tử mới từ một mảng cho trước với các phần tử mới là các gía trị thỏa mãn với khối lệnh thực hiện trong block. Ví dụ:
 ["a", "b",  "c"].collect { |letter| letter.capitalize }  => result:  ["A", "B", "C"]
 có thể viết gắn gọn và đẹp code hơn như sau: ["a", "b", "c"].collect(&:capitalize)
  1. SELECT Ta thường dùng khi muốn tạo ra một danh sách các phần tử mới từ một mảng cho trước. select trong rails rất đơn giản và dễ sử dụng. Nó lặp qua các phần tử trong mảng và kiểm tra logic. Nếu logic trả về TRUE nó sẽ gán phần tử đó vào 1 mảng mới và tiếp tục thực hiện lại cho đến khi lặp qua hết các phần tử của mảng.

  2. REJECT Như đã nói phía trên, reject giống như là anh em cùng cha khác mẹ của select vậy =)) Cũng dùng khi ta muốn tạo ra một danh sách các phần tử mới từ một mảng cho trước, tuy nhiên nó hoạt động ngược lại so với select, nghĩa là thay vì logic trả về TRUE thì ở đây logic trả về FALSE nó mới gán phần tử vào mảng. Hẳn là ta đã nhìn rõ sự khác biệt ngay từ cái ví dụ đầu tiên được đưa ra trong bài viết này.

  3. DETECT Dùng khi ta muốn tìm kiếm một phần tử trong mảng. Với việc dùng detect ta sẽ lấy ra đúng phần tử ta cần bằng một điều kiện đặt trong khối lệnh block. Ví dụ:

a = [1, 2, 3, 4]
a.detect { |n|  n ==  2 }

a.detect {|n| n > 2}  => result: 3

Điều đáng lưu ý ở đây là, khi dùng detect mà kết qủa tìm kiếm với điều kiện trong khối lệnh block mà nhiều hơn một kết qủa thì sẽ lấy kết qủa gần nhất.

  1. INJECT

Inject takes a value and a block, and it runs that block once for each element of the list.

đây là điều khác biệt so với map.

Sử dụng select, reject hoặc collect thì kết quả trả về chính là một mảng. Nếu như muốn trả về một thứ gì đó khác, hay muốn tính toán các phần tử trong mảng(cộng, trừ, nhân chia, tổng các số chẵn, lẻ…) thì inject là lựa chọn thích hợp để sử dụng.

Sự khác biệt chính giữa selectinject chính là inject cung cấp cho bạn một biến khác để sử dụng ngay trong đoạn code. Biến này được sử dụng để lưu trữ các giá trị mới của mỗi lần lặp. Các giá trị này là kết quả của logic bạn đặt trong khối block xử lý. Sau mỗi lần lặp lại, bất cứ điều gì nó tính toán ra đều được thêm vào biến đó. Ví dụ:

a = [1, 2, 3, 4]
a.inject { |sum, n|  sum + n }
=> result: sum = 10

// khởi tạo gía trị ban đầu cho biến sum = 20
a.inject(20) { |sum, n|  sum + n } => result: sum = 30

//Ngoài ra, ta có thể trả về 1 mảng hay 1 hash mới khi dùng inject nếu ta khởi tạo gía trị ban đầu cho biến là Array hay Hash
a.inject([]) { |arr, n|  arr << n*n }  => result:  [1, 4, 9, 16]

Tóm lại, Dùng select hay reject khi ta cần tạo ra một mảng mới với việc lựa chọn hay loại bỏ những element của Array dựa theo một điều kiện nào đó. Ta dùng collect khi muốn build một thằng array với những kết qủa trả về từ logic của khối lệnh block. Dùng inject khi cần thao tác hay thực hiện các phép toán tích lũy, tính tổng, ghép các gía trị của mảng lại với nhau. Có thể hiểu như là sẽ tạo ra 1 thằng kết qủa duy nhất sau những thao tác đấy. Dùng detect khi cần tìm kiếm một phần tử trong mảng.

Hi vọng bài viết sẽ hữu ích cho bạn. Thanks for your reading!


All Rights Reserved