2010-07-03 8 views
1

Для реализации автоматического вивификация рубина хэш, можно использовать следующий классКак обрабатывать комбинацию [] + = для автоживущего хеша в Ruby?

class AutoHash < Hash 
    def initialize(*args) 
    super() 
    @update, @update_index = args[0][:update], args[0][:update_key] unless 
args.empty? 
    end 

    def [](k) 
    if self.has_key?k 
     super(k) 
    else 
     AutoHash.new(:update => self, :update_key => k) 
    end 
    end 

    def []=(k, v) 
    @update[@update_index] = self if @update and @update_index 
    super 
    end 

    def few(n=0) 
    Array.new(n) { AutoHash.new } 
    end 
end 

Этот класс позволяет сделать следующие вещи

a = AutoHash.new 
a[:a][:b] = 1 
p a[:c] # => {}    # key :c has not been created 
p a  # => {:a=>{:b=>1}} # note, that it does not have key :c 

a,b,c = AutoHash.new.few 3 
b[:d] = 1 
p [a,b,c] # => [{}, {:d=>1}, {}] # hashes are independent 

Существует a bit more advanced definition этого класса proposed by Joshua, который для меня немного сложно понять.

Проблема

Существует одна ситуация, когда я думаю, что новый класс может быть улучшен. Следующий код выдает сообщение об ошибке NoMethodError: undefined method '+' for {}:AutoHash

a = AutoHash.new 
5.times { a[:sum] += 10 } 

Что бы вы сделали, чтобы справиться с этим? Можно ли определить оператора []+=?


Похожие вопросы

  1. Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP?
  2. Multiple initialization of auto-vivifying hashes using a new operator in Ruby ruby hash initialization r
  3. все еще открыт:How to create an operator for deep copy/cloning of objects in Ruby?
+1

Что вы пытаетесь сделать с последний фрагмент кода, и что вы хотите? Если вы хотите, чтобы 'a [: sum]' равнялось 50, вам нужно будет инициализировать его до нуля до строки '5.times', потому что нет возможности для AutoHash знать, что вы хотите сохранить в нем цифры , – Adrian

ответ

5

Там нет никакого способа defin e a []+= метод в рубине. Что происходит, когда вы набираете

x[y] += z 

является

x[y] = x[y] + z 

так оба [] и []= методы вызываются на x+ вызывается x[y], который в данном случае является AutoHash). Я думаю, что лучший способ справиться с этой проблемой - определить метод + на AutoHash, который просто вернет его аргумент. Это сделает AutoHash.new[:x] += y работы практически для любого типа y, потому что «пустая» версия y.class ('' для строк, 0 для цифр, ...) плюс y почти всегда будет равна y.

class AutoHash 
    def +(x); x; end 
end 

Добавим, что метод будет сделать оба эти работы:

# Numbers: 
a = AutoHash.new 
5.times { a[:sum] += 10 } 
a[:sum] #=> 50 

# Strings: 
a = AutoHash.new 
5.times { a[:sum] += 'a string ' } 
a[:sum] #=> "a string a string a string a string a string " 

И кстати, здесь чище версия кода:

class AutoHash < Hash 
    def initialize(args={}) 
    super 
    @update, @update_index = args[:update], args[:update_key] 
    end 

    def [](k) 
    if has_key? k 
     super(k) 
    else 
     AutoHash.new :update => self, :update_key => k 
    end 
    end 

    def []=(k, v) 
    @update[@update_index] = self if @update and @update_index 
    super 
    end 

    def +(x); x; end 

    def self.few(n) 
    Array.new(n) { AutoHash.new } 
    end 
end 

:)

+0

Прохладный! Большое спасибо, Адриан! На мгновение я думал о случае, когда 'a [: sum]' уже имеет значение другого типа, но потом я понимаю, что такое повышение исключения - это хорошо. – Andrei

+1

@Andrei: Когда 'a [: sum]' имеет значение, которое не является «AutoHash», исключение будет поднято. Например: 'a = AutoHash.new; a [: x] = 5; a [: x] + = 'str'' вызовет исключение. – Adrian

0
require 'xkeys' # on rubygems.org 

a = {}.extend XKeys::Hash 
a[:a, :b] = 1 
p a[:c] # => nil (key :c has not been created) 
p a # => { :a => { :b => 1 } } 

a.clear 
5.times { a[:sum, :else => 0] += 10 } 
p a # => { :sum => 50 } 
1

Я думаю, что вы хотите:

hash = Hash.new { |h, k| h[k] = 0 }

hash['foo'] += 3 # => 3

Это вернет 3, затем 6 и т.д. без ошибок, так как новое значение по умолчанию присваивается 0.