Reduce trong Ruby
Reduce là một function của Enumerable, tuy nhiên với nhiều Rubyists function này rất ít khi được sử dụng. Mọi người thường sử dụng reduce khi muốn tính tổng. [1, 2, 3].reduce :+ Nhưng từ Ruby 2.4.x trở lên thì chúng ta đã có sum làm việc đó [1, 2, 3].sum Nếu vậy chẳng nhẽ reduce trở lên ...
Reduce là một function của Enumerable, tuy nhiên với nhiều Rubyists function này rất ít khi được sử dụng. Mọi người thường sử dụng reduce khi muốn tính tổng.
[1, 2, 3].reduce :+
Nhưng từ Ruby 2.4.x trở lên thì chúng ta đã có sum làm việc đó
[1, 2, 3].sum
Nếu vậy chẳng nhẽ reduce trở lên vô dụng rồi sao ? Không phải vậy, bí mật của reduce là Mọi chức năng Enumerable có thể được cài đặt trong giới hạn của reduce
Trong bài viết này tôi sẽ trình bày về cách thức sử dụng của reduce và một số hàm khác tương tự trong ruby.
Map
Map được sử dụng cho để áp dụng một function cho một tập các Enumerable.
[1,2,3].map { |i| i * 2 } # => [2,4,6]
Lưu ý: map không làm thay đổi array gốc, mà chỉ return về một mảng mới đã được biến đổi theo function.
Select
Giống như map, ngoại trừ việc function của nó như một điều kiện, nếu điều kiện function đúng thì nó sẽ giữ nguyên element cho danh sách mới, nếu không thì loại bỏ nó đi
[1,2,3].select { |i| i > 1 } # => [2, 3]
Reduce
Từ đây, tôi sẽ phân tích chi tiết cách hoạt động của reduce Ví dụ, chúng ta phân tích đoạn code sau đây:
[1,2,3].reduce(0) { |accumulator, i| accumulator + i } # => 6
[1,2,3].reduce(0) đầu tiên gán giá trị khởi tạo là 0 { |accumulator, i| accumulator + i } truyền vào 2 tham số accumulator và i. Giá trị đầu tiên của accumulator sẽ là giá trị khởi tạo ban đầu hoặc phần tử đầu tiên của list. Với mỗi chu kỳ, accumulator được gán giá trị là giá trị trả về của chu kỳ cuối.
a | i | reducer ---+---+----------- 0 | 1 | 0 + 1 : 1 1 | 2 | 1 + 2 : 3 3 | 3 | 3 + 3 : 6 6 | - | - ---+---+----------- Returns: 6
Về cơ bản, trống nó giống như thế này ((((0) + 1) + 2) + 3)
Map bằng reduce
Reduce không quan tâm tới giá trị khởi tạo ban đầu là loại gì ? trong ví dụ trước chúng ta dùng giá trị khởi tạo là integer, nhưng chúng ta hoàn toàn có thể sử dụng data type bất kỳ nhưng array, string... Với reduce, chúng ta có thể tạo ra một map bằng cách sử dụng giá trị khởi tạo là array. Ví dụ:
def map(list, &fn) list.reduce([]) { |a, i| a.push(fn[i]) } end map([1,2,3]) { |i| i * 2 } # => [2, 4, 6]
Đây là cách hoạt động của function map tạo bằng reduce
a | i | fn[i] | reduction --------+---+-----------+--------------- [] | 1 | 1 * 2 : 2 | [].push(2) [2] | 2 | 2 * 2 : 4 | [2].push(4) [2,4] | 3 | 3 * 2 : 6 | [2,4].push(6) [2,4,6]| - | - | - --------+---+-----------+---------------- Returns: [2, 4, 6]
Select bằng reduce
Tương tự map, cũng khá dễ dàng để tạo select
def select(list, &fn) list.reduce([]) { |a, i| fn[i] ? a.push(i) : a } end select([1,2,3]) { |i| i > 1 } # => [2, 3]
Đây là cách hoạt động của select tạo bằng reduce
a | i | fn[i] | reduction -------+---+---------------+--------------------------- [] | 1 | 1 > 1 : false | false ? [].push(i) : [] [] | 2 | 2 > 1 : true | true ? [].push(i) : [] [2] | 3 | 3 > 1 : true | true ? [2].push(i) : [2] [2,3] | - | - | - -------+---+---------------+--------------------------- Returns: [2, 3]
Find bằng reduce
Trong trường hợp của find, ta chỉ muốn trả về một giá trị tương ứng với điều kiện thích hợp, vì vậy ta không cần duyệt hết list, mà chỉ cần duyệt đến khi nào tìm thấy giá trị thích hợp. Ta có thể sử dụng break trong trường hợp này.
def find(list, &fn) list.reduce(nil) { |_, i| break i if fn[i] } end find([1,2,3]) { |i| i == 2 } # => 2 find([1,2,3]) { |i| i == 4 } # => nil
Cách thức haojt động của find tạo bằng reduce
a | i | fn[i] | reduction -----+---+----------------+------------------ nil | 1 | 1 == 2 : false | break i if false nil | 2 | 2 == 2 : true | break i if true -----+---+----------------+------------------ Break: 2 a | i | fn[i] | reduction -----+---+----------------+------------------ nil | 1 | 1 == 4 : false | break i if false nil | 2 | 2 == 4 : false | break i if false nil | 3 | 3 == 4 : false | break i if false nil | - | - | - -----+---+----------------+------------------ Returns: nil
Combining Functions
Có thể nói, với reduce, chúng ta có thể xây dựng được chức năng của map, select, find và nó còn có thể làm được với nhiều function khác nữa. Chẳng hạn việc kết hợp các function với nhau kiểu map_compact hoặc map_select...
Ví dụ hàm map_compact:
def map_compact(list, &fn) list.reduce([]) { |a, i| value = fn[i] value.nil? ? a : a.push(value) } end map_compact([1,2,3]) { |i| i > 1 ? i * 2 : nil } # => [4, 6]
Nó giống với select, nhưng nó có thể return một list các phần tử được lọc đồng thời tùy biến lại các phần tử đó.
a | i | fn[i] | reduction -------+---+---------------------+------------------------------- [] | 1 | 1 > 1 : nil | nil.nil? ? [] : [].push(1) [] | 2 | 2 > 1 : 2 * 2 : 4 | 4.nil? ? [] : [].push(4) [4] | 3 | 3 > 1 : 3 * 2 : 6 | 6.nil? ? [4] : [4].push(6) [4,6] | - | - | - -------+---+---------------------+------------------------------- Returns: [4, 6]
Tham khảo
https://medium.com/@baweaver/reducing-enumerable-the-basics-fa042ce6806