今天看到了這篇文章--Five Ruby Methods You Should Be Using,感受收穫頗豐,先簡單翻譯一下先。html
做者寫這篇文章的契機是在Exercism上看到了不少ruby代碼能夠用更好的方式去重構,所以他分享了一些冷門的可是很是有用的ruby方法。git
你是否曾發如今某個對象上調用方法時返回值不是你所預期?你想返回這個對象,可是返回的時候又想對這個對象進行一些修改。比方說,你想給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
方法,通常的規律是對那些在對象上調用,但願返回對象,可是卻沒返回該對象自己的方法都適用。編程
我不清楚你的狀況,但我常常在數組裏去查找數據。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
字段來查找從數據庫中取出的數據。
考慮這種狀況,你有個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了。
想當年我在一個技術訓練營,咱們的導師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。