你應該學會使用的5個ruby方法

今天看到了這篇文章--Five Ruby Methods You Should Be Using,感受收穫頗豐,先簡單翻譯一下先。html

做者寫這篇文章的契機是在Exercism上看到了不少ruby代碼能夠用更好的方式去重構,所以他分享了一些冷門的可是很是有用的ruby方法。git

Object#tap

你是否曾發如今某個對象上調用方法時返回值不是你所預期?你想返回這個對象,可是返回的時候又想對這個對象進行一些修改。比方說,你想給hash對象增長1個key value,這時候你須要調用Hash.[]方法,可是你想返回的是整個hash對象,而不是具體的某個value值,所以你須要顯示的返回該對象。github

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

最後一行的那個params顯得有些多餘了。算法

咱們能夠用Object#tap方法來優化這個方案。sql

tap方法用起來很是簡單,直接在某個對象上調用tap方法,而後就能夠在代碼塊裏yielded這個對象,最後這個對象自己會被返回。下面的代碼演示瞭如何使用tap方法來重構剛纔的實現。數據庫

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

有不少地方均可以使用到Object#tap方法,通常的規律是對那些在對象上調用,但願返回對象,可是卻沒返回該對象自己的方法都適用。編程

Array#bsearch

我不清楚你的狀況,但我常常在數組裏去查找數據。ruby的enumerable模塊提供了不少簡單好用的方法select, reject, find。不過當數據源很龐大的時候,我開始對這些查找的性能表示憂桑。數組

若是你正在使用ActiveRecord和非NO SQL的數據庫,查詢的算法複雜度是通過優化了的。可是有時候你須要從數據庫裏把全部的數據拉出來進行處理,比方說若是你加密了數據庫,那就不能好好的寫sql作查詢了。ruby

這時候我會左思右想以找到一個最小的算法複雜度來篩選數據。若是你不瞭解算法複雜度,也就是這個O,請閱讀Big-O Notation Explained By A Self-Taught Programmer或[Big-O Complexity Cheat Sheet](http://bigocheatsheet.com/)。數據結構

通常來講,算法複雜度越低,程序運行的速度就越快。O(1), O(log n), O(n), O(n log(n)), O(n^2), O(2^n), O(n!),在這個例子裏,越往右算法複雜度是越高的。因此咱們要讓咱們的算法接近左邊的複雜度。

當咱們搜索數組的時候,通常第一個想到的方法即是Enumerable#find,也就是select方法。不過這個方法會搜索整個數組直到找到預期的結果。若是要找的元素在數組的開始部分,那麼搜索的效率倒不會過低,但若是是在數據的末尾,那麼搜索時間將是很可觀的。find方法的算法複雜度是O(n)。

更好的辦法是使用(Array#bsearch)[http://www.ruby-doc.org/core-2.1.5/Array.html#method-i-bsearch]方法。該方法的算法複雜度是O(log n)。你能夠查看Building A Binary Search這篇文章來該算法的原理。

下面的代碼顯示了搜索50000000個數字時不一樣算法之間的性能差別。

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)

如你所見,bsearch要快的多。不過要注意的是bsearch要求搜索的數組是排序過的。儘管這個限制bsearch的使用場景,bsearch在顯示生活中確實是有用武之地的。好比經過created_at字段來查找從數據庫中取出的數據。

Enumerable#flat_map

考慮這種狀況,你有個blog應用,你但願找到上個月有過評論的全部做者,你能夠會這樣作:

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

獲得的結果看起來會是這樣的

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

不過你想獲得的是全部做者,這時候你大概會使用flatten方法。

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

另外一個選擇是使用flat_map方法。

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

這跟使用flatten方法沒什麼太大的不一樣,不過看起來會優雅一點,畢竟不須要反覆調用flatten了。

Array.new with a Block

想當年我在一個技術訓練營,咱們的導師Jeff Casimir同志(Turing School的創始人)讓咱們在一小時內寫個Battleship遊戲。這是極好的進行面向對象編程的練習,咱們須要Rules,Players, Games和Boards類。

建立表明Board的數據結構是一件很是有意思的事情。通過幾回迭代我發現下面的方法是初始化8x8格子的最好方式:

class Board
  def board
    @board ||= Array.new(8) { Array.new(8) { '0' } }
  end
end

上面的代碼是什麼意思?當咱們調用Array.new並傳入了參數length,1個長度爲length的數組將會被建立。

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

當你傳入一個block,這時候block的返回值會被當成是數組的每一個元素。

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

所以,當你向block傳入1個具備8個元素的數組時,你會獲得8x8個元素的嵌套數組了。

用Array#new加block的方式能夠建立不少有趣和任意嵌套層級的數組。

<=>

這個方法就很常見了。簡單來講這方法是判斷左值和右值的關係的。若是左值大於右值返回1,相等返回0,不然返回-1。

實際上Enumerable#sort, Enumerable#max方法都是基於<=>的。另外若是你定義了<=>,而後再include Comparable,你將免費獲得<=, <, >=, >以及between方法。

這是做者的在現實生活中所用到的例子:

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

這個方法不是很好理解,大概的意思就是若是minutes超過60的話,小時數+1,等於60小時數不變,不然-1。

討論

會的方法越多寫出來的代碼可能會更有表現力,邊寫代碼邊改進,另外多讀rubydoc。

相關文章
相關標籤/搜索