12/08/2018, 16:57

Một số hàm xử lí object trong ruby bạn nên biết.

Xin chào các bạn, ruby là một ngôn ngữ hỗ trợ rất nhiều các hàm dựng sẵn hay bên trong các class, trong quá trình làm việc đôi khi chúng ta cần các hàm với chức năng tương tự nhưng lại không biết dẫn đến tốn nhiều thời gian cho việc code lại hàm mới, bài viết này mình sẽ tổng hợp một số hàm xử lí ...

Xin chào các bạn, ruby là một ngôn ngữ hỗ trợ rất nhiều các hàm dựng sẵn hay bên trong các class, trong quá trình làm việc đôi khi chúng ta cần các hàm với chức năng tương tự nhưng lại không biết dẫn đến tốn nhiều thời gian cho việc code lại hàm mới, bài viết này mình sẽ tổng hợp một số hàm xử lí trong ruby với các ví dụ cụ thể. Hy vọng bài viết sẽ giúp ích cho các bạn trong việc tối ưu và làm đẹp code.

1. Object#tap Có khi nào bạn gặp một tình huống cần xử lí các thông tin bên trong 1 đối tượng và muốn thay đổi chính đối tượng đó, thay vì trả về một đối tượng mới không?. Ví dụ bạn có 1 Hash, sau khi xử lí các value của hash, bạn phải thêm 1 dòng trả về chính hash đó. Cách viết này nhìn có vẻ không đẹp mắt và code không được tối ưu lắm, ví dụ như sau:

def update_params(params)
  params[:foo] = 'bar'
  params
end

Bạn truyển params là 1 hash vào trong hàm, sau khi gán key "foo" bằng value "bar" ta phải trả về params. Thay vì như thế, chúng ta có thể dùng hàm tap, truyền vào 1 block và object đó sẽ tự động được trả về như sau:

def update_params(params)
  params.tap {|p| p[:foo] = 'bar' }
end

#tap là 1 hàm có mặt trong rất nhiều Class, bạn có thể sử dụng ở các class khác với mục đích tương tự.

2.Array#bsearch Mảng là một kiểu dữ liệu được sử dụng rất phổ biến trong lập trình, thông thường với số lượng phần tử nhiều chúng ta có thể sử dụng các hàm trong ruby như find, reject, select với module Enumerable, các hàm này sẽ xử lí lấy ra các phần tử lần lượt, giúp duyệt mảng nhanh hơn và không bị tràn. Tuy nhiên với khối lượng dữ liệu lưu vào mảng rất lớn thì các phương pháp trên vẫn chưa được tối ưu. Gỉa sử ta có 1 mảng có 50.000.000 phần tử, ta cần tìm ra các phần tử có gía trị lớn hơn 40.000.000, nếu sử dụng hàm select của enumerable, chúng ta cần duyệt tuần tự qua 50.000.000 phần tử đó, độ phức tạp của thuật toán lúc này là O(n). Với việc sử dụng hàm bsearch của ruby, độ phức tạp của thuật toán trên chỉ còn O(log n). Đây là kết qủa khi chạy thử benchmark trên phiên bản ruby 1.9


require 'benchmark'

data = (0..50_000_000)

Benchmark.bm do |x|
  x.report(:find) { data.find {|number| number > 40_000_000 } }
  x.report(:bsearch) { data.bsearch {|number| number > 40_000_000 } }
end

         user       system     total       real
find     3.020000   0.010000   3.030000   (3.028417)
bsearch  0.000000   0.000000   0.000000   (0.000006)

Như các bạn có thể thấy, bsearch nhanh hơn rất nhiều, tuy nhiên 1 điểm rất quan trọng cần lưu ý đó là bsearch đó là mảng cần phải được sắp xếp trước đó. Các bạn có thể cân nhắc sử dụng trong 1 số trường hợp như mảng id, created_at... được load ra từ database.

3. Enumerable#flat_map Khi làm việc với dữ liệu quan hệ, đôi khi chúng ta cần lấy ra một mảng các thuộc tính của một đối tượng nào đó. Gỉa sử bạn có 1 blog và bạn đang muốn lấy ra tên của tất cả những người đã comment vào các bài viết của 1 vài người dùng nào đó, code của bạn lúc đó trông sẽ như sau.


module CommentFinder
  def self.find_for_users(user_ids)
    users = User.where(id: user_ids)
    users.map do |user|
      user.posts.map do |post|
        post.comments.map |comment|
          comment.author.username
        end
      end
    end
  end
end

Và đây là kết qủa trả về:

[[['Ben', 'Sam', 'David'], ['Keith']], [[], [nil]], [['Chris'], []]]

Rõ ràng là các phần tử trong mảng lồng nhau qúa nhiều mà không theo từng bài viết, thông thường chúng ta sử dụng flattern ở các lần map để duỗi các mảng như sau:


module CommentFinder
  def self.find_for_users(user_ids)
    users = User.where(id: user_ids)
    users.map { |user|
      user.posts.map { |post|
        post.comments.map { |comment|
          comment.author.username
        }.flatten
      }.flatten
    }.flatten
  end
end

Tuy nhiên nếu sử dụng flat_map, bạn sẽ không cần sử dụng flattern nữa, nó giúp code chúng ta trông gọn gàng và chuyên nghiệp hơn:


module CommentFinder
  def self.find_for_users(user_ids)
    users = User.where(id: user_ids)
    users.flat_map { |user|
      user.posts.flat_map { |post|
        post.comments.flat_map { |comment|
          comment.author.username
        }
      }
    }
  end
end

Kết qủa thu được tương tự như khi dùng flattern nhiều lần             </div>
            
            <div class=

0