2013-04-05 3 views
4

У меня есть следующий код:Невозможно изменить замороженную Fixnum на 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 без проблем. Что случилось?

+1

Подтверждено код работает нормально на 1.9 .3 Также подтверждено, что не работает в 2.0.0 для меня. Добавление переменной экземпляра в Fixnum довольно необычно. –

+0

Пытался ускорить работу. Некоторые методы могут занимать пару секунд, и все это снова и снова повторяется для большого набора чисел, что увеличивает время выполнения. – nicooga

+0

'prime' - это драгоценный камень? какой оператор находится в строке 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

Я согласен, что важно следить за освещением, особенно если вы пишете библиотечный код. Как ваш подход, так и моя определяют 'divisors' и' divisors_sum' в 'Numeric'. Единственным дополнительным методом является mixin 'fcache'. – dbenhur

4

В дополнение к ответу @ Тадман, тем причина, почему работает в 1.9.3, а не в 2.0.0 потому, что 2 года назад было принято решение заморозить Fixnums (и Bignums) о чем свидетельствует this и this.

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

+0

Умный раствор. Однако я был бы обеспокоен сбором мусора. Я думаю, что циклическая зависимость означает, что int никогда не будет GC'd (а также не будет элементом из кеша). – mahemoff

+0

@mahemoff Это характер простых кешей, которые всегда растут. Если это вызывает беспокойство, замените LRU или TTL или другой истекающий кэш для простых рубиновых хэшей в примере. Или используйте [WeakRef] (https://ruby-doc.org/stdlib-2.3.1/libdoc/weakref/rdoc/WeakRef.html) и обработайте возможное исключение в поиске. – dbenhur