在Ruby方法中创建缓存

时间:2015-02-10 21:09:49

标签: javascript ruby memoization

JavaScript 中,记住像Fibonacci这样的函数非常简单:

// In JavaScript

var fibonacci = (function () {
  var cache = {}; // cache for future calculations

  return function (num) {
    if (num < 0)    throw new Error('Negative numbers not allowed');
    if (num === 0)  return 0;
    if (num === 1)  return 1;

    cache[num] = cache[num] || fibonacci(num - 1) + fibonacci(num - 2);
    return cache[num];
  };
})();

console.log( fibonacci(5) ); // results in 5
console.dir( fibonacci ); // you can inspect the closure scope and see that the cache object saves the values for future use

我正在尝试理解如何在 Ruby 中做类似的事情,不幸的是,我唯一能想到的就是创建一个类并将缓存存储为类变量:

# In Ruby
class Placeholder
  @@cache = {}

  def fibonacci(num)
    raise 'Negative numbers not allowed' if num < 0
    return 0 if num == 0
    return 1 if num == 1

    @@cache[num] ||= fibonacci(num - 1) + fibonacci(num - 2)
  end
end

example = Placeholder.new
puts example.fibonacci(5) # results in 5

我不喜欢这样的事实是,当我不打算创建Placeholder的实例时,我正在创建一个类结构。相反,我只是这样做,因为我想将状态保存在Ruby类变量中。理想情况下,如果我能够创建module并拥有module变量,那么这至少可以解决我使用基于class的解决方案进行实例化的“问题”。 在Ruby中这样做的最佳建议是什么?

根据@ meagar的评论进行更新:

@meagar,你在暗示这样的事情吗?

class Placeholder
  attr_reader :cache

  def initialize
    @cache = {}
  end

  def fibonacci(num)
    raise 'Negative numbers not allowed' if num  < 0
    return 0 if num == 0
    return 1 if num == 1

    @cache[num] ||= fibonacci(num - 1) + fibonacci(num - 2)
  end
end

FibonacciCalculator = Placeholder.new
puts FibonacciCalculator.fibonacci(5) # results in 5

我已经比我最初的Ruby解决方案更喜欢这个了,尽管让Placeholder类仍然以错误的方式让我感到厌烦。

4 个答案:

答案 0 :(得分:4)

当您不需要实例时,可以使用单{{1>} {/ 1>}:

Module

请注意,对于缓存,module Fibonacci @cache = {} def self.series(num) if @cache[num] then return @cache[num]; end if num < 0 then raise 'Negative numbers not allowed'; end if num == 0 then return 0; end if num == 1 then return 1; end @cache[num] = series(num - 1) + series(num - 2) end end puts Fibonacci.series(5) # results in 5 模块上的实例变量与类变量一样有效(对于某些扩展用途,它可能更好)。它的工作原理是因为模块Fibonacci是Fibonacci的一个实例 - 它在这方面与任何其他实例变量相同。

答案 1 :(得分:3)

您的ECMAScript版本的字面翻译将是:

fibonacci = -> {
  cache = {} # cache for future calculations

  -> num {
    raise ArgumentError, 'Negative numbers not allowed' if (num < 0)
    return 0 if num.zero?
    return 1 if num == 1

    cache[num] ||= fibonacci.(num - 1) + fibonacci.(num - 2)
  }
}.()

fibonacci.(5)
# => 5
fibonacci.binding.local_variable_get(:cache)
# => {2=>1, 3=>2, 4=>3, 5=>5}

顺便说一下,我们可以做一些简化:如果0num则返回0,而1则返回num1,如果numnum0(或1),我们就可以返回num <= 1。事实上,我们可以通过简单地使用cache0的值初始化1来完全摆脱整个条件。此外,缓存只能是Array,因为索引只是Integer s的连续范围:

fibonacci = -> {
  cache = [0, 1] # cache for future calculations

  -> num {
    raise ArgumentError, 'Negative numbers not allowed' if (num < 0)
    cache[num] ||= fibonacci.(num - 1) + fibonacci.(num - 2)
  }
}.()

有趣的是,如果我们在现代ECMAScript中写出来,那么这种关系就变得很明显了:

&#13;
&#13;
const fibonacci = (() => {
    const cache = [0, 1, 1]; // cache for future calculations

    return num => {
        if (num < 0) throw new Error('Negative numbers not allowed');
        return cache[num] = cache[num] || fibonacci(num - 1) + fibonacci(num - 2);
    };
})();

console.log(fibonacci(5));
&#13;
&#13;
&#13;

在旧学校ECMAScript中会是这样的:

&#13;
&#13;
var fibonacci = function () {
    var cache = [0, 1, 1]; // cache for future calculations

    return function (num) {
        if (num < 0) throw new Error('Negative numbers not allowed');
        return cache[num] = cache[num] || fibonacci(num - 1) + fibonacci(num - 2);
    };
}();

console.log(fibonacci(5));
&#13;
&#13;
&#13;

答案 2 :(得分:2)

  

...当我不打算创建占位符

的实例时,我是一个类结构

嗯,这是你的问题。

Ruby是一种面向对象的语言。您不能拥有不属于对象的函数。 在对象上调用每个方法。

您只需创建Placeholder的实例(并为其指定适当的名称,如FibonacciCalculator),并使您的缓存成为该对象的简单实例变量。

答案 3 :(得分:2)

您还可以使用闭包来存储缓存,这类似于javascript的操作方式。

def memoize func
  cache = {}
  proc do |*args|
    next cache[args] if cache[args]
    cache[args] = func[*args]
  end
end

def slow_double x
  sleep 2
  x * 2
end

memoized_double = memoize(method :slow_double)

memoized_double[4] # takes 2 seconds
memoized_double[4] # returns instantly