今天看到了这篇文章--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。