Memoization in Ruby
阿新 • • 發佈:2019-01-10
這裡的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]
快取方法結果:
舉個最簡單常用的例子:
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
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
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]