12/08/2018, 13:38

Recursion in Ruby

Đệ quy là gì? Recursion in computer science is a method where the solution to a problem depends on solutions to smaller instances of the same problem (as opposed to iteration). Graham, Ronald; Donald Knuth; Oren Patashnik (1990). Concrete Mathematics. Chapter 1: Recurrent Problems. Ví dụ ...

Đệ quy là gì?

Recursion in computer science is a method where the solution to a problem depends on solutions to smaller instances of the same problem (as opposed to iteration).
Graham, Ronald; Donald Knuth; Oren Patashnik (1990). Concrete Mathematics. Chapter 1: Recurrent Problems.

Ví dụ mã giả đệ quy

def sum_array
  if the array is empty
    return 0
  else
    head of array + sum_array(tail of array)
  end
end

Quan sát ví dụ tính tổng các phần tử trong mảng.

Flow cơ bản của hàm đệ quy trên như sau:

  • Thực hiện một số thao tác với phần tử đầu tiên trong mảng.
  • Gọi chức năng tương tự với các phần tử còn lại của mảng.
  • Có điều kiện để dừng quá trình gọi đệ quy khi đã đạt được kết quả mong muốn.

Dưới đây là một ví dụ kết quả của đoạn mã giả trên.

array = [1,2,3,4,5]

1 + 2 + 3 + 4 + 5
1 + 2 + 3 + 9
1 + 2 + 12
1 + 14
15

Recursion vs Iteration

Trong Ruby người ta thường tránh sử dụng đệ quy và sử dụng vòng lặp để thay thế, Ruby có cấu trúc ngôn ngữ rất hữu dụng để hỗ trợ iteration trên dữ liệu. Recursion có thể kết thúc chậm hơn và tốn nhiều tài nguyên hơn so với Iteration. Trên Stack Overflow cũng có một số thảo luận về vấn đề này tại đây

Ở một số ngôn ngữ, đặc biệt là các ngôn ngữ lập trình chức năng (functional languages) đối với dữ liệu không thay đổi (immutable data) Elixir, Haskell ... Không có cấu trúc ngôn ngữ nào có thể giải quyết tất cả bằng iteration mà phải thực hiện thông qua đệ quy. Những ngôn ngữ này được tối ưu hóa cho đệ quy bằng việc sử dụng kỹ thuật tối ưu hóa tail callvà đi kèm với là thuật toán đối sánh mẫu pattern matching để giải quyết vấn đề head and tails của mảng. (Bạn có thể đọc thêm tại tails call và parttern matching)

Why recursion in Ruby

Bỏ qua những vấn đề tôi đã nói ở trên, tôi muốn nhìn thấy những gì recursion thực hiện để giải quyết một số vấn đề trong Ruby. Tôi sử dụng kỹ thuật monkey patching để thêm một method head_tail cho class Array.

class Array
  def head_tail
    head, *tail = *self
    [head, tail]
  end
end

Ví dụ: Array [1,2,3,4,5], the head is 1 and the tail is [2,3,4,5].

Recursion Examples

Factorials

Tính 6!, kết quả sẽ là 6 * 5 * 4 * 3 * 2 * 1

def recur_fact(num)
  if num == 0 || num == 1
    1
  else
    num * recur_fact(num - 1)
  end
end

recur_fact(6) # 720

Với iteration.

def iter_fact(num)
  if num == 0 || num == 1
    1
  else
    sum = 1
    num.times do |n|
      sum *= (n + 1)
    end
    sum
  end
end

iter_fact(6) # 720

Summing Array

def recur_sum(array)
  if array.empty?

  else
    head, tail = array.head_tail
    head + recur_sum(tail)
  end
end

recur_sum([1,2,3,4,5]) # 15

Với Iteration

def iter_sum(array)
  sum = 0
  array.each do |elem|
    sum += elem
  end
  sum
end

iter_sum([1,2,3,4,5]) # 15

Mapping Array

def recur_map(array, f)
  if array.empty?
    []
  else
    head, tail = array.head_tail
    [f.(head)] + recur_map(tail, f)
  end
end

recur_map([1,2,3,4,5], ->(elem) {
  elem * elem
}) # [1, 4, 9, 16, 25]

Với iteration

def iter_map(array, f)
  new_array = []
  array.each do |elem|
    new_array << f.(elem)
  end
  new_array
end

iter_map([1,2,3,4,5], ->(elem) {
  elem * elem
}) # [1, 4, 9, 16, 25]

Reducing Array

def recur_reduce(array, acc, f)
  if array.empty?
    acc
  else
    head, tail = array.head_tail
    recur_reduce(tail, f.(acc, head), f)
  end
end

# Join
recur_reduce(["Leigh", "Christopher", "Halliday"], "", ->(r, elem) {
  "#{r} #{elem}".strip
}) # "Leigh Christopher Halliday"

# Sum
recur_reduce([1,2,3,4,5], 0, ->(r, elem) {
  r + elem
}) # 15

# Longest String
recur_reduce(["Leigh", "Christopher", "Halliday"], "", ->(r, elem) {
  r.length > elem.length ? r : elem
}) # "Christopher"

# Count
recur_reduce(["Leigh", "Christopher", "Halliday"], 0, ->(r, elem) {
  r + 1
}) # 3

# Map
recur_reduce([1,2,3,4,5], [], ->(r, elem) {
  r + [elem * elem]
}) # [1, 4, 9, 16, 25]

Tổng kết

Trên đây là bài tìm hiểu chung về đệ quy và đệ quy trong Ruby, mong nhận được sự góp ý tích cực của mọi người.

Tham khảo

https://www.leighhalliday.com/recursion-in-ruby

https://viblo.asia/thanhnc/posts/zoZVRg9LGmg5

http://kipalog.com/posts/Gioi-thieu-ve-Pattern-Matching-trong-Elixir

0