Reduce trong Ruby
Bài đăng này đã không được cập nhật trong 7 năm
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
All rights reserved