12/08/2018, 14:47

Five Ruby Methods You Should Be Using

Ai đó đã từng nói "Ruby will teach you to express your ideas throught a computer" . Có lẽ đó là lý do tại sao Ruby trở thành sự lựa chọn phổ biến cho phát triển web hiện đại. Cũng như trong tiếng anh, trong Ruby có rất nhiều cách để nói về những điều tương tự nhau. Tôi dành khá nhiều thời gian ...

Ai đó đã từng nói "Ruby will teach you to express your ideas throught a computer" . Có lẽ đó là lý do tại sao Ruby trở thành sự lựa chọn phổ biến cho phát triển web hiện đại. Cũng như trong tiếng anh, trong Ruby có rất nhiều cách để nói về những điều tương tự nhau. Tôi dành khá nhiều thời gian cho việc đọc và soi code người khác trên Exercism, và tôi nhận thấy rằng các vấn đề được giải quyết đơn giản hóa rất nhiều nếu người viết biết được về các method tồn tại trong Ruby. Dưới đây là các method trong Ruby ít được biết đến nhưng dùng để giải quyết vấn đề khá tốt Object#tap Đã bao giờ bạn thấy mình gọi một method trên một số đối tượng, và giá trị trả về không phải là cái bạn muốn. Giá trị mong muốn trả về là một object , nhưng thay vào đó bạn nhận được một giá trị khác. Với ví dụ dưới đây là cách bạn muốn hàm trả về đúng đối tượng bạn mong muốn.

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

Ta thấy dòng code params kết thúc method trông chả liên quan và khá ngu. Chúng ta có thể làm sạch code với Object#tap Nó sử dụng khá dễ dàng, chỉ cần gọi ngay trên object bạn muốn trả về. Trong tap block là đoạn code xử lý mà bạn muốn chạy.

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

Array#bsearch Làm việc với dữ liệu mảng, trong Ruby enumerables có các công cụ select, reject and find cho ta sử dụng. Nhưng khi dữ liệu lớn, hàng triệu phần tử trong mảng thì tôi bắt đầu lo lắng về hiệu năng, thời gian thực thi. Nếu bạn sử dụng ActiveRecord và SQL database, có rất nhiều điều kỳ diệu ẩn đăng sau nhằm mục đích làm cho các tìm kiếm của bạn được tiến hành bằng thuật toán với độ phức tạp nhỏ nhất. Nhưng thỉnh thoảng bạn phải kéo toàn bộ dữ liệu trong database ra trước khi bạn có thể làm việc với nó. Độ phức tạp thuật toán được sắp xếp theo thứ tự tăng dần: O(1), O(log n), O(n), O(n log(n)), O(n^2), O(2^n), O(n!) Khi bạn tìm kiếm trong Array với ngôn ngữ Ruby, thì phương thức đầu tiên bạn nghĩ tới là Enumerable#find. Tuy nhiên, method này sẽ tìm kiếm từ đầu tới cuối danh sách cho tới khi phần tử đầu tiên được tìm thấy. Sẽ thật tuyệt vời nếu phần tử cần tìm ở ngay đầu danh sách, vậy nếu trường hợp phần từ ở cuối danh sách thì sao. Độ phức tạp để tìm thấy giá trị là O(n). Một cách khác nhanh hơn, thay vào sử dụng find ta sử dụng Array#bsearch, bạn có thể tìm thấy phần tử cần tìm với độ phức tạp O(log n). Bạn có thể tìm hiểu về cách làm việc của Binary search Building A Binary Search Bây giờ ta sẽ cùng xem sự khác biệt của việc tìm kiếm thông qua 2 cách tiếp cận với khoảng 50,000,000 phần tử.

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ư bạn thấy, bsearch nhanh hơn nhiều so với find. Tuy nhiên, nhược điểm khá lớn của bsearch đó là mảng phải được sắp xếp. Điều này làm hạn chế phần nào sự hữu dụng của nó.

Array#flatten Khi làm việc với dữ liệu quan hệ, đôi lúc bạn phải thu thập các thuộc tính, và phải trả về dạng mảng mà không được lồng nhau. Tưởng tượng bạn có 1 blog và bạn cần tìm ra những người đã comment bài viết của bạn trong tháng trước.

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

Kết quả nhận về là:

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

Bạn muốn mảng không lồng nhau, đơn giản với flatten

[[['Ben', 'Sam', 'David'], ['Keith']], [[], [nil]], [['Chris'], []]].flatten
=> ["Ben", "Sam", "David", "Keith", nil, "Chris"]

Array.new with a Block Khi bạn sử dụng

Array.new(8)
#=> [nil, nil, nil, nil, nil, nil, nil, nil]

Bạn muốn truyền các giá trị khởi tạo mặc định cho mảng của mình

Array.new(8) { 'O' }
#=> ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O']

<=> Cơ bản về spaceship đó là thay thế kết quả trả về.

a <=> b
  if a < b then return -1
  if a = b then return  0
  if a > b then return  1
  if a and b are not comparable then return nil

Câu hỏi đặt ra ở đây là bạn có thể ứng dụng nó như nào. Có một bài tập được gọi là Clock, bạn cần điều chỉnh giờ phút đồng hồ sử dụng các method + và - Khó khăn ở đây là khi bạn cố gắng thêm hơn 60 phút, bởi vì nó làm cho giá trị phút invalid. Vì vậy bạn phải tăng giá trị giờ lên 1 đơn vị và trừ đi 60 từ giá trị phút.

  def fix_minutes minutes
    until (0...60).member? minutes
      @hours -= 60 <=> minutes
      @minutes += 60 * (60 <=> minutes)
    end
    @hours %= 24
    self
  end

Bài viết được dịch tại Engineyard

0