3

I could memoize a Ruby function, by using a local scope and a closure:

require "benchmark" fib = ->(n) { return 0 if n < 0 return 1 if n == 0 return fib.(n-1) + fib.(n-2) } memoize = ->(f) { already_know = {} new_function = ->(n) { already_know[n] ||= f.(n) return already_know[n] } return new_function } fib = memoize.(fib) puts Benchmark.measure { p fib.(42) } 

and it took 0.000011 second to run. Without the line fib = memoize.(fib), it would take 259 seconds to run.

But can you do the same thing with a method (instead of a function) in Ruby? It seems like a Python method is more like a function because you can easily do that with a Python method while a Ruby's method is less like a function -- maybe because a method in Ruby is not an object. But the question is, can you do something like the memoize in the code above to make a method become memoized?

5
  • 1
    checkout 4 Simple Memoization Patterns in Ruby using @instance_variable Commented Jan 5, 2016 at 11:08
  • I'm confused. You show Ruby (?) code to do a thing, and then ask how to do it in Ruby? Commented Jan 5, 2016 at 11:10
  • @JonathonReinhart it is function vs method Commented Jan 5, 2016 at 11:11
  • You can convert methods to to procs, e.g. put_fn = Kernel.method(:puts).to_proc. Commented Jan 5, 2016 at 11:17
  • @sschmeck can you use that and put in back to the class? (like redefining the method) Commented Jan 5, 2016 at 11:19

3 Answers 3

2

You can alias the original method and add a simple caching to original implementation.

class Fib def fib(n) case when n < 0 then 0 when n == 0 then 1 else fib(n-1) + fib(n-2) end end end puts Benchmark.measure { p Fib.new.fib(35) } class Fib alias_method :fib_ori, :fib def fib(n) (@fib_cache ||= {})[n] ||= fib_ori(n) end end puts Benchmark.measure { p Fib.new.fib(35) } 
Sign up to request clarification or add additional context in comments.

1 Comment

seems like it works... not like functional programming... using an instance variable instead of a scope and a closure, but it works. If there is a solution that doesn't need to introduce an extra instance variable...
1

Similar to sschmeck's solution, but via inheritance:

class Fibonacci def at(n) return 0 if n < 0 return 1 if n == 0 return at(n - 1) + at(n - 2) end end class MemoizedFibonacci < Fibonacci def initialize @memo = {} end def at(n) @memo[n] ||= super end end 

3 Comments

the last return in at method looks redundant :)
@AndreyDeineko explicit return is sometimes used to improve readability even if it is syntactically unnecessary.
@naomik I am only 3 years with Ruby, but I've never seen someone intentionally use redundant return. For me personally it does not improve readability but hurts the eyes :)
1

Module#prepend was added in Ruby 2+ specifically in because it can (among other things) act as a method combinator/decorator similar to CLOS or Python. In that way, you don't actually need to get access to the method itself, you can just override it.

class Module def memoize(meth) prepend(Module.new do memo = {} define_method(meth) do |*args, &blk| memo[[self, *args, blk]] ||= super(*args, &blk) end end) end end class Integer memoize def fib raise ArgumentError if self < 0 return self if self < 2 pred.fib + pred.pred.fib end end require 'benchmark' puts Benchmark.measure { p 42.fib } 

In older versions of Ruby (1.9 or older), you would have to do something like this:

class Module def memoize(meth) memo = {} old_meth = instance_method(meth) define_method(meth) do |*args, &blk| memo[[self, *args, blk]] ||= old_meth.bind(self).(*args, &blk) end end end 

Also, def evaluating to a Symbol denoting the name of the method being defined was added in Ruby 2.2, so, in older versions, you have to do this instead:

class Integer def fib raise ArgumentError if self < 0 return self if self < 2 pred.fib + pred.pred.fib end memoize :fib end 

We could use a trick such as the one Rake uses for its desc method, though, to make it memoize the next method being defined:

class Module def memoize(meth=nil) return @__memoize_next_method__ = true unless meth memo = {} old_meth = instance_method(meth) define_method(meth) do |*args, &blk| memo[[self, *args, blk]] ||= old_meth.bind(self).(*args, &blk) end end def method_added(meth) return if @__recursing__ @__recursing__ = true # protect against infinite recursion if @__memoize_next_method__ memoize(meth) @__memoize_next_method__ = nil end @recursing = nil end end class Integer memoize def fib raise ArgumentError if self < 0 return self if self < 2 pred.fib + pred.pred.fib end end 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.