1. 程式人生 > >Memoization in Ruby

Memoization in Ruby

這裡的Memoization就是將ruby的方法或lambda返回值快取起來的技術。

快取方法結果:

舉個最簡單常用的例子:
Ruby程式碼

1. class ApplicationController < ActionController::Base
2. def current_user
3. User.find(session[:user_id])
4. end
5. end

class ApplicationController < ActionController::Base
def current_user
User.find(session[:user_id])
end
end


vs.

Ruby程式碼

1. class ApplicationController < ActionController::Base
2. def current_user
3. @current_user ||= User.find(session[:user_id])
4. end
5. end

class ApplicationController < ActionController::Base
def current_user
@current_user ||= User.find(session[:user_id])
end
end


下面程式碼的好處顯而易見,在一次請求中最多隻呼叫一次取session和查db操作。


||=這個操作適用於單行賦值操作。是ruby的慣用法之一,一般情況下ruby的||=就可以解決類似的快取功能。但是情況並不是那麼簡單,看下面程式碼:

Ruby程式碼

1. def may_i_help_u?
2. @result ||= begin
3. 此處是一個耗時很長的判斷。。
4. puts "我被執行了!"
5. false
6. end
7. end

def may_i_help_u?
@result ||= begin
此處是一個耗時很長的判斷。。
puts "我被執行了!"
false
end
end


連續兩次呼叫該方法:

Ruby程式碼

1. may_i_help_u? => 我被執行了!.
2. may_i_help_u? => 我被執行了!

may_i_help_u? => 我被執行了!.
may_i_help_u? => 我被執行了!


由於||=操作的特性,這裡對nil和false返回就不能做快取了。。還有一個缺點就是不能快取帶引數的方法。。


更通用的memoization:


Ruby程式碼

1. def some_method(*args)
2. @some_method ||= {}
3. @some_method[args] ||= (
4. 這裡要等很久
5. )
6. end

def some_method(*args)
@some_method ||= {}
@some_method[args] ||= (
這裡要等很久
)
end


用方法名對應的例項變數儲存結果集,結果集為ruby的hash,不同的引數對應相應的返回值。

這個實現雖然支援了引數形式,缺點同樣是不能快取nil和false返回值。


繼續改造:


Ruby程式碼

1. def some_method(*args)
2. @some_method ||= {}
3. return @some_method[args] if @some_method.has_key?(args)
4.
5. @some_method[args] = (
6. 這裡要等很久
7. )
8. end

def some_method(*args)
@some_method ||= {}
return @some_method[args] if @some_method.has_key?(args)

@some_method[args] = (
這裡要等很久
)
end


嗯,可以快取nil或false返回值了,也可以支援多引數了。不過如果每個方法裡這麼寫也有點煩躁。。


還好ActiveSupport已經寫好了這個擴充套件,使用起來也很方便:

Ruby程式碼

1. extend ActiveSupport::Memoizable
2.
3. def zipcode_and_name
4. "{zipcode} {name}"
5. end
6. memoize :zipcode_and_name

extend ActiveSupport::Memoizable

def zipcode_and_name
"{zipcode} {name}"
end
memoize :zipcode_and_name


Memoization類方法也很簡單,ruby裡類方法只不過是Class的例項,只需開啟metaclass:


Ruby程式碼

1. class << self
2. extend ActiveSupport::Memoizable
3.
4. def a_class_method
5. some code
6. end
7. memoize :a_class_method
8. end

class << self
extend ActiveSupport::Memoizable

def a_class_method
some code
end
memoize :a_class_method
end


ActiveSupport::Memoizable的實現方式和上面的思路一樣,不過用了很多超程式設計技巧,使用起來才這麼方便。

PS:最近面試總有些面試官喜歡問超程式設計,問神馬是超程式設計,ruby裡怎樣超程式設計。。答案無非是“程式執行時動態改變自身”“method_missing、各種eval、define_method、反射等等”

我覺得還不如找些簡單功能說說實現方案/思路來的實在。。


這個memoizable同樣都不能正確處理帶block引數的方法。因為這個memoization本身就不是萬金油,也不能濫用。。特別是當方法引數和返回不是一一對映時,比如這兩個方法:


隨機數:

Java程式碼

1. def rank(n)
2. rand(n*n)
3. end

def rank(n)
rand(n*n)
end


與動態資料緊密相關的:

Ruby程式碼

1. def age
2. today = Date.today
3. today.year - birth_date.year + (today.month - birth_date.month + ((today.day - birth_date.day) < 0 ? -1 : 0) < 0 ? -1 : 0)
4. end

def age
today = Date.today
today.year - birth_date.year + (today.month - birth_date.month + ((today.day - birth_date.day) < 0 ? -1 : 0) < 0 ? -1 : 0)
end


快取Proc/lambda結果:

Via:《Ruby程式語言》

Ruby程式碼

1. module Functional
2. def memoize
3. cache = {}
4. lambda {|*args|
5. unless cache.has_key? args
6. cache[args] = self[*args]
7. end
8. cache[args]
9. }
10. end
11. alias
[email protected]
memoize
12.
13. end
14.
15. Proc.send(:include, Functional)
16.
17. fac = lambda{|x| return 1 if x == 0; x * fac[x - 1];}.memoize
18. 或fac = +lambda{|x| return 1 if x == 0; x * fac[x - 1];}

module Functional
def memoize
cache = {}
lambda {|*args|
unless cache.has_key? args
cache[args] = self[*args]
end
cache[args]
}
end
alias
[email protected]
memoize

end

Proc.send(:include, Functional)

fac = lambda{|x| return 1 if x == 0; x * fac[x - 1];}.memoize
或fac = +lambda{|x| return 1 if x == 0; x * fac[x - 1];}


ruby的Hash是個好東西,可以傳遞Proc做引數,並且快取結果。

引用
Hash 對映和 Proc 對映區別在於: Hash 帶有快取機制,而 Proc 不快取結果。

[b]在建專案:www.viila.net歡迎各位大俠攻擊指教[/b]