在Ruby方法中创建缓存

Creating a cache in Ruby methods

本文关键字:创建 缓存 方法 Ruby      更新时间:2023-09-26

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中,你对此有什么最好的建议

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

@可怜虫,你是在建议这样的事情吗?

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类仍然会让我感到不舒服。

当您不需要实例时,可以使用带有singleton方法的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是Module的一个实例——在这方面,它与任何其他实例变量都一样。

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}

顺便说一句,我们可以做一些简化:如果num0,则不返回0,如果num1,则返回1;如果num01(或num <= 1),则只返回num。事实上,我们可以通过简单地用01的值预初始化cache来完全消除整个条件。此外,高速缓存可以只是Array,因为索引只是Integers:的连续范围

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中写下这一点,那么这种关系就变得显而易见:

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

在老式的ECMAScript中,哪个会是这样的:

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

当我并不真正打算创建Placeholder 实例时的类结构

好吧,这是你的问题。

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

您应该简单地创建一个Placeholder的实例(并给它一个合适的名称,如FibonacciCalculator),并使缓存成为该对象上的一个简单实例变量。

您也可以使用闭包来存储缓存,这与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