2013-04-05 3 views
4

에 냉동 Fixnum이라는 수정할 수 없습니다 나는 다음과 같은 코드가 있습니다루비 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에 인스턴스 변수를 추가하는 것은 아주 드문 일입니다. –

+0

procces의 속도를 높이려고했습니다. 어떤 방법은 몇 초가 걸릴 수 있으며 많은 숫자를 반복해서 계산하면 실행 시간이 길어집니다. – nicooga

+0

'프라임'은 보석입니까? 어떤 성명서가 24 번 줄에 있니? –

답변

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

+0

그 접근법은 더 많은 인스턴스 메소드를 작성하는 부작용이 있습니다. 핵심 수업을 수정하는 경우 가능한 한 작게 발자국을 유지하는 것이 가장 좋습니다. – tadman

+0

특히 라이브러리 코드를 작성하는 경우 풋 프린트를 유지하는 것이 중요하다는 데 동의합니다. 여러분의 접근 방식과 광산 방식 모두'Numeric' 내에'divisors'와'divisors_sum'을 정의합니다. 유일한 추가 방법은 mixedin'fcache '입니다. – dbenhur

4

@의 tadman의 대답 이외에, 이유는 왜 2 년 전 결정이 thisthis에 의해 입증으로 Fixnums (및 Bignums)를 동결하기로 결정했습니다 때문에 2.0.0가에 1.9.3에서 작동하지.

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

나쁘지는 않지만 논리를 '숫자'로 유지하는 것이 좋습니다. – nicooga

+0

영리한 해결책. 나는 쓰레기 수거에 대해 걱정할 것이다. 순환 의존성은 int가 GC 화되지 않을 것임을 의미합니다 (또한 캐시의 항목도 아님). – mahemoff

+0

@mahemoff 항상 커지는 단순한 캐시의 특성입니다. 이것이 문제라면, 예제에서 간단한 루비 해시를 LRU 또는 TTL 또는 다른 만료 캐시로 대체하십시오. 또는 [WeakRef] (https://ruby-doc.org/stdlib-2.3.1/libdoc/weakref/rdoc/WeakRef.html)를 사용하고 조회에서 가능한 예외를 처리하십시오. – dbenhur