2013-04-05 75 views
4

我有以下代碼:不能修改凍結對長整數紅寶石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 

至極輸出誤差:

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

當我刪除緩存到實例變量錯誤消失@divisors@divisors_sum ...等。這隻發生在紅寶石2.0。在1.9.3上運行沒有問題。發生了什麼事?

+1

確認代碼工作正常上1.9 .3也確認在2.0.0中對我無效。然而,將一個實例變量添加到Fixnum是非常不尋常的。 – 2013-04-05 17:54:52

+0

試圖加快procces。有些方法可能需要幾秒鐘,並且反覆計算大量數字會使執行時間增加。 – nicooga 2013-04-05 18:06:14

+0

'素'是寶石?哪條語句在#24行? – 2013-04-05 18:13:04

回答

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

[另一種方法](http://stackoverflow.com/a/15842123/1074296)可以保持類中的邏輯被擴展並將實例數據移入外部模塊。 – dbenhur 2013-04-05 19:41:12

+0

該方法具有創建更多實例方法的副作用。在修改核心類時,儘可能減少佔用空間總是最好的。 – tadman 2013-04-05 20:54:49

+0

我同意保持足跡清淡至關重要,特別是在編寫庫代碼時。你的方法和我的'Numeric'都定義了'divisors'和'divisors_sum'。唯一的附加方法是混合'fcache'。 – dbenhur 2013-04-06 02:42:40

4

除了@ tadman的答案,之所以工程1.9.3而不是在2.0.0是因爲2年前作出決定,凍結就證明了thisthis的Fixnums(和大數)。

4

正如其他人指出的紅寶石核心決定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]}} 
+0

不錯,我喜歡把邏輯放在'Numeric'中的想法。 – nicooga 2013-04-05 19:46:20

+0

聰明的解決方案。儘管我會關心垃圾收集。我認爲循環依賴意味着int永遠不會GC'd(緩存中的項也不會)。 – mahemoff 2016-10-30 02:30:38

+0

@mahemoff這是簡單的緩存總是增長的性質。如果這是一個問題,請在示例中用一個LRU或TTL或其他到期的緩存代替簡單的ruby散列。或者使用[WeakRef](https://ruby-doc.org/stdlib-2.3.1/libdoc/weakref/rdoc/WeakRef.html)並在查找中處理可能的異常。 – dbenhur 2016-10-31 20:07:29