4

我有以下代码:

require 'prime'
class Numeric
  #... math helpers

  def divisors
    return [self] if self == 1
    @divisors ||= prime_division.map do |n,p|
      (0..p).map { |i| n**i }
    end.inject([1]) do |a,f|
      a.product(f)
    end.map { |f| f.flatten.reduce(:*) } - [self]
  end

  def divisors_sum
     @divisors_sum ||= divisors.reduce(:+)
  end

   #... more methods that evaluate code and 'caches' and assigns (||=) to instance variables
end

输出错误:

> 4.divisors
/home/cygnus/Projects/project-euler/stuff/numbers.rb:24:in `divisors_sum': can't modify frozen Fixnum (RuntimeError)

当我将缓存删除到实例变量中时,错误消失了@divisors@divisors_sum...等等。这只发生在 ruby​​ 2.0 上。在 1.9.3 上运行它没有问题。发生了什么?

4

3 回答 3

4

@divisors是 Fixnum 实例上的实例变量,因此您正在尝试更改它。你可能不应该这样做。

那这个呢?

module Divisors
  def self.for(number)
    @divisors ||= { }
    @divisors[number] ||= begin
      case (number)
      when 1
        [ number ]
      else
        prime_division.map do |n,p|
          (0..p).map { |i| n**i }
        end.inject([1]) do |a,f|
          a.product(f)
        end.map { |f| f.flatten.reduce(:*) } - [ number ]
      end
    end
  end

  def self.sum(number)
     @divisors_sum ||= { }
     @divisors_sum[number] ||= divisors(number).reduce(:+)
  end
end

class Numeric
  #... math helpers

  def divisors
    Divisors.for(self)
  end

  def divisors_sum
     Divisors.sum(self)
  end
end

这意味着 Numeric 中的方法不会修改任何实例,缓存存储在其他地方。

于 2013-04-05T18:30:34.627 回答
4

除了@tadman 的回答之外,工作1.9.3而不是工作的原因2.0.0是因为 2 年前决定冻结 Fixnums (和 Bignums),正如thisthis所证明的那样。

于 2013-04-05T18:42:50.057 回答
4

正如其他人指出的那样,ruby 核心已经决定 Fixnums 和 Bignums 现在被冻结,所以你不能在这些类的对象中设置实例变量。

一种解决方法是制作一个外部模块,该模块保留由这些冻结对象的值索引的哈希缓存,并使用这些哈希的元素而不是实例变量:

require 'prime'

module FrozenCacher
  def FrozenCacher.fcache
    @frozen_cache ||= {}
  end

  def fcache
    FrozenCacher.fcache[self] ||= {}
  end
end

class Numeric
  include FrozenCacher
  #... math helpers

  def divisors
    return [self] if self == 1
    fcache[:divisors] ||= prime_division.map do |n,p|
      (0..p).map { |i| n**i }
    end.inject([1]) do |a,f|
      a.product(f)
    end.map { |f| f.flatten.reduce(:*) } - [self]
  end

  def divisors_sum
    fcache[:divisors_sum] ||= divisors.reduce(:+)
  end

   #... more methods that evaluate code and 'caches' and assigns (||=) to instance variables
end

puts 4.divisors.inspect           # => [1, 2]
puts FrozenCacher.fcache.inspect  # => {4=>{:divisors=>[1, 2]}}
puts 10.divisors.inspect          # => [1, 5, 2]
puts FrozenCacher.fcache.inspect  # => {4=>{:divisors=>[1, 2]}, 10=>{:divisors=>[1, 5, 2]}}
于 2013-04-05T19:36:49.710 回答