Iterator trong Ruby

Ở bài trước mình có nói với các bạn về việc sử dụng các vòng lặp for, vòng lặp while, hay until. Chúng rõ ràng là những cốt lõi trong ngôn ngữ lập trình Ruby, thế nhưng những cách viết vậy có lẽ vẫn hơi thủ công và cồng kềnh. Trong bài này mình sẽ giới thiệu với các bạn các cách để làm việc với vòng lặp dễ dàng và nhanh hơn

1. Iterator là gì?

Iterators là các công cụ vòng lặp giúp chúng ta thực hiện truy cập vào các phần tử của một tập các đối tượng một cách tuần tự.

Cụ thể ở đây Ruby cung cấp cho chúng ta các phương thức để làm việc với các collection như là Array hay là Hash. Collection giúp chúng ta lưu dữ một tập hợp các dữ liệu.

Có rất nhiều các iterator trong Ruby đã được định nghĩa sẵn như each, map, select, find… Để có thể hiểu rõ hơn rằng khi nào dùng iterator nào thì chúng ta sẽ lần lượt đi tìm hiểu các iterator này nhé.

Bài viết này được đăng tại [kiso.vn]

2. Các iterators trong Ruby

each

each cũng giống như cách mà chúng ta làm việc với các vòng lặp thông thường như for, while hay until. Nó sẽ lần lượt truy cập tới các phần tử trong collection. Và vấn đề đáng lưu tâm nhất ở đây là each sẽ trả về mảng ban đầu chứ không phải là mảng kết quả.

Cú pháp

collection.each do |variable_name|
   # code
end

hoặc nếu những đoạn code bên trong đơn giản chúng ta có thể viết tắt như này, điều này sẽ áp dụng tương tự cho các iterator khác.

collection.each {|variable_name| // code }

collection ở đây các bạn có thể truyền vào là một Range, Array hoặc Hash đều được cả nhé.

[1, 2, 3, 4].each do |e|
  puts e * 2
end

Kết quả:

2
4
6
8

Tại sao mà mình là nói là each sẽ trả về mảng ban đầu thay vì mảng kết quả. Ví dụ nếu chúng ta muốn lấy ra các phần tử có giá trị lớn hơn 2 trong mảng chả hạn với each thì theo như logic chúng ta sẽ viết như thế này

a = [1,2, 3, 4]

a.each do |e|
  e > 2
end

puts "Mang a #{a}"

Kết quả

Mang a [1, 2, 3, 4]

Nên là nếu như chúng ta muốn lấy vài phần tử trong mảng cho trước thì dùng each sẽ không phù hợp.

map

Khác với each, thì kết quả của map sẽ trả về một mảng mới sau khi đã lặp qua các phần tử của một collection, kết quả trả về sẽ tùy thuộc vào cách mà chúng ta xử lý collection đó. Nhưng mà lưu ý là mảng ban đầu sẽ không thay đổi giá trị nhé.

Cú pháp:

collection.map do |variable_name|
   # code
end

Ví dụ

[1,2, 3, 4].map do |e|
   e * 2
end

Kết quả

2
4
6
8

Nếu như chúng ta muốn lấy các giá trị mà thỏa mãn với các điều kiện cho trước thì kết quả sẽ trả về là một mảng các giá trị true hoặc false

[1,2,3,4,5,6,7,8,9,10].map{ |e| e > 5 }

Kết quả

false
false
false
false
false
true
true
true
true
true

select

select cũng là một trong những iterator phổ biến thường được dùng. Nó sẽ lặp qua lần lượt các phần tử trong collection và trả về 1 mảng mới thỏa mãn điều kiện được định nghĩa trong block.

Cú pháp

collection.select do |variable_name|
   # code
end

Giả sử chúng ta muốn trả về các kết quả nếu giá trị nhỏ hơn 5 thì sử dụng select sẽ trả lại cho kết quả hợp lý

[1,2,3,4,5,6,7,8,9,10].select{ |e| e > 5 }

Kết quả

6, 7, 8, 9, 10

Chúng ta không thể lặp lại mảng rồi thay đổi giá trị của các phần tử trong mảng nếu như sử dụng select

[1,2,3,4,5,6,7,8,9,10].select{ |e| e * 5 }

Kết quả

1,2,3,4,5,6,7,8,9,10

collect

collect thì về cơ bản là giống map. Nó cũng lặp qua lần lượt các phần tử trong mảng và thực hiện các biểu thức trong block

[1,2, 3, 4].collect {|e| e * 2}

Kết quả

2, 4, 6, 8

Nó cũng sẽ trả về một mảng các giá trị true hoặc false nếu như chúng ta chọn các giá trị theo điều kiện

[1, 2, 3, 4].collect {|e| e > 5}

Kết quả

false, false, false, false

inject

inject sẽ giúp chúng ta tính tổng các giá trị trong mảng.

inject có thể nhận tham số truyền vào là một giá trị nào đó. Nếu như chúng ta không truyền giá trị nào thì mặc định tổng ban đầu của mảng sẽ lấy giá trị đầu tiên của mảng làm tổng. Nếu truyền giá trị khác thì tổng ban đầu của mảng sẽ bằng giá trị chúng ta truyền vào.

Ví dụ

[1,2,3,4,5,6,7,8,9,10].inject{ |sum, e| sum += e }

Kết quả

55

Nếu chúng ta truyền thêm tham số vào inject

[1,2,3,4,5,6,7,8,9,10].inject(15){ |sum, e| sum += e }

Kết quả

70

detect

detect là một iterator khá hay, nó sẽ trả về giá trị đầu tiên thỏa mãn điều kiện trong block

[1,2,3,4,5,6,7,8,9,10].detect{ |e| e > 4 }

Kết qủa

5

Nếu như biểu thức bên trong block không phải là một điều kiện thì mặc định nó sẽ trả về phần tử đầu tiên của mảng

[1,2,3,4,5,6,7,8,9,10].detect{ |e| e * 2 }

Kết quả

1

Ngoài hàm detect này ra thì chúng ta còn có hàm find với chức năng tương tự

[1,2,3,4,5,6,7,8,9,10].find{ |e| e > 4 }

Kết quả

5

Có thể thấy, find và detect sẽ đều không lặp quả tất cả các phần tử nếu như một phần tử trong mảng thỏa mãn điều kiện, thì vòng lặp này kết thúc luôn. Việc này chúng ta cũng có thể liên tưởng khi sử dụng break để thoát khỏi vòng lặp.

each_with_index

each_with_index thì tương tự với phương thức each mà mình đã nói tới ở trên. Tuy nhiên với each_with_index cho phép chúng ta truyền thêm một tham số nữa, đó là chỉ số (index) của các phần tử mảng. Trong rất nhiều các trường hợp mà chúng ta sẽ cần tới cái index này.

Cú pháp

collection.each_with_index {|item, index| // code }

Ví dụ

[1,2,3,4,5].each_with_index { |item, index| puts "index: #{index} for #{item}" }

Kết quả

index: 0 for 1
index: 1 for 2
index: 2 for 3
index: 3 for 4
index: 4 for 5

times

Iterator times này chúng ta sẽ không áp dụng trên các collection như các phương thức ở trên, mà với iterator này chúng ta sẽ áp dụng trên một số. Có nghĩa là chúng ta sẽ lặp lại một đoạn code với số lần mà chúng ta định nghĩa

Cú pháp

number.times do
  //code
end

Ví dụ

5.times do 
  puts "Hello"
end

Kết quả

Hello
Hello
Hello
Hello
Hello

3. Kết luận

Qua bài viết này chúng ta có thể thấy được sức mạnh của các iterator trong Ruby là như thế nào. Đây là những iterator rất phổ biến và hay được sử dụng trong việc coding. Hãy kiểm tra kĩ các trường hợp mà bạn cần xử lý để áp dụng các iterator một cách chính xác và nhanh gọn nhất có thể. Iterator trong Ruby là rất phong phú và đa dạng nên hãy áp dụng nó thay vì viết các vòng lặp đơn thuần như for, while, until vì đôi khi chúng hơi làm “phình to” code của chúng ta.

Bài viết liên quan

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *