无法在Ruby 2.0上修改冻结的Fixnum

时间:2013-04-05 17:46:40

标签: ruby ruby-2.0

我有以下代码:

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

Wich输出错误:

> 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上没有问题。发生了什么事?

3 个答案:

答案 0 :(得分: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中的方法不会修改任何实例,缓存会存储在别处。

答案 1 :(得分:4)

除了@ tadman的回答之外,之所以在1.9.3而不是2.0.0中工作,是因为2年前决定冻结Fixnums(和Bignums),如{{{1}}所示。 3}}和this

答案 2 :(得分:4)

正如其他人所指出的,ruby core已经决定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]}}