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


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

作者寫這篇文章的契機是在Exercism上看到了很多ruby代碼可以用更好的方式去重構,因此他分享了一些冷門的但是非常有用的ruby方法。

Object#tap

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

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

最后一行的那個params顯得有些多余了。

我們可以用Object#tap方法來優化這個方案。

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做查詢了。

這時候我會冥思苦想以找到一個最小的算法復雜度來篩選數據。如果你不了解算法復雜度,也就是這個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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM