我有以下代码:
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上没有问题。发生了什么事?
答案 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]}}