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
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 }