你能处理Ruby的方法,如函数式编程,比如记忆函数吗?

时间:2016-01-05 10:59:26

标签: ruby functional-programming memoization

我可以通过使用本地范围和闭包来记忆Ruby函数:

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) }

并且需要0.000011秒才能运行。如果没有行fib = memoize.(fib),则需要259秒才能运行。

但是你可以用Ruby中的方法(而不是函数)做同样的事情吗?看起来Python方法更像是一个函数,因为你可以使用Python方法轻松实现,而Ruby的方法不像函数 - 也许是因为Ruby中的方法不是对象。但问题是,您是否可以执行上述代码中的memoize之类的操作来使方法成为备忘录?

3 个答案:

答案 0 :(得分:2)

您可以为原始方法添加别名,并为原始实现添加简单缓存。

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) }

答案 1 :(得分:1)

sschmeck's solution类似,但是通过继承:

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

答案 2 :(得分:1)

Module#prepend在Ruby 2+中被特别添加,因为它可以(除其他外)充当类似于CLOS或Python的方法组合器/装饰器。这样,您实际上不需要访问方法本身,您可以覆盖它。

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 }

在旧版本的Ruby(1.9或更早版本)中,您必须执行以下操作:

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

此外,在Ruby 2.2中添加了def评估表示所定义方法名称的Symbol,因此,在旧版本中,您必须改为:

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

我们可以使用诸如Rake用于其desc方法的技巧,以使其记住下一个定义的方法:

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