rails ruby hash operator-keyword autovivification

ruby - rails - haml to html



¿Cómo manejar la combinación[]+= para el hash auto vivificante en Ruby? (3)

Para implementar la auto-vivificación de hash Ruby, se puede emplear la siguiente clase

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

Esta clase permite hacer las siguientes cosas

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

Hay una definición un poco más avanzada de esta clase propuesta por Joshua , que es un poco difícil de entender para mí.

Problema

Hay una situación en la que creo que se puede mejorar la nueva clase. El siguiente código falla con el mensaje de error NoMethodError: undefined method ''+'' for {}:AutoHash

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

¿Qué harías para manejarlo? ¿Se puede definir []+= operador?

Preguntas relacionadas

  1. ¿Es posible la inicialización automática de matrices hash multidimensionales en Ruby, como en PHP?
  2. Hash inicialización de hash auto vivificante usando un nuevo operador en Ruby Ruby hash initialization r
  3. sigue abierto: ¿Cómo crear un operador para la copia / clonación profunda de objetos en Ruby?

Lo que creo que quieres es esto:

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

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

Eso devolverá 3, luego 6, etc. sin un error, porque el nuevo valor está predeterminado 0.


No hay forma de definir un método []+= en ruby. Qué pasa cuando escribes

x[y] += z

es

x[y] = x[y] + z

por lo que los métodos [] y []= se invocan sobre x (y se AutoHash + sobre x[y] , que en este caso es un AutoHash ). Creo que la mejor manera de manejar este problema sería definir un método + en AutoHash , que simplemente devolverá su argumento. Esto hará que AutoHash.new[:x] += y funcione para casi cualquier tipo de y , porque la versión "vacía" de y.class ( '''' para cadenas, 0 para números, ...) más y será casi siempre igual y .

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

Agregar ese método hará que ambos funcionen:

# 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 "

Y, por cierto, aquí hay una versión más limpia de tu código:

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

:)


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 }