so whats so magical about this Hash?
h = Lang::Hash.new()
h.get['some']['long']['nonexistant']['nesting'].or 'mydefaultvalue' #gives [mydefaultvalue]
with ordinary hash, same code wold look like this:
h = Hash.new()
h['some'] ||= {}
h['some']['long'] ||= {}
h['some']['long']['nonexistant'] ||= {}
h['some']['long']['nonexistant']['nesting'] || 'mydefaultvalue' #gives [mydefaultvalue]
setting a value to nonexistant hash is done like this:
h.get['some']['long']['nonexistant']['nesting'] = 'actual value'
h.get['some']['long']['nonexistant']['nesting'].or 'mydefaultvalue' #gives [actualvalue]
module Lang
class Hash < ::Hash
def get
HashValue.new(self)
end
def [] k
unless keys.include? k
self[k] = Hash.new
end
super
end
def or(other)
if empty?
other
else
self
end
end
end
class HashValue < Hash
attr_accessor :value
def initialize value
@value = value
end
def [] k
v = value[k]
# if v.is_a? Hash
# v
# else
HashValue.new(v)
# end
end
def []= k, v
value[k] = v
end
def or other
#p @value
if @value.is_a?(Hash) && !@value.empty?
#p [:a, @value]
@value
elsif !@value.is_a?(Hash) && @value
#p [:b, @value]
@value
else
#p :c
other
end
end
end
end
class HashTest < Test::Unit::TestCase
include Lang
def test_hash_nil_value_or_returns_other
h = Hash.new
val = h['nonexistant']
assert_equal 'val-was-false', (val.or 'val-was-false')
end
def test_hash_value_or_returns_value
h = Hash.new
h['a'] = 'a value'
val = h.get['a']
p val
assert_equal 'a value', (val.or 'b value')
end
def test_hash_value_can_set_unset_stuff
h = Hash.new
h.get['a'] = 'value'
assert_equal 'value', h.get['a'].or('nil get')
assert_equal 'value', h['a']
end
def test_hash_value_can_set_unset_stuff_nested
h = Hash.new
h.get['a']['b'] = 'value'
assert_equal 'value', h['a']['b']
assert_equal 'value', h.get['a']['b'].or('nil get')
end
def test_hash_value_can_set_unset_stuff_more_nested
h = Hash.new
h.get['a']['b']['c'] = 'value'
assert_equal 'value', h['a']['b']['c']
assert_equal 'value', h.get['a']['b']['c'].or('nil get')
end
end
Refactorings
No refactoring yet !
Mortice
October 8, 2009, October 08, 2009 08:54, permalink
The following will get you to 3 levels of nesting but not 4. I feel like there's probably a neat trick to get to N levels using a similar technique to the code below, but I can't quite see it yet...
class NilClass
def [](any)
nil
end
end
def create_hash
Hash.new do |hash, key|
hash[key] = Hash.new({})
end
end
h = create_hash
h['a']['b']['c'] or "default" # => "default"
h = create_hash
h['a']['b']['c'] = "hello"
h['a']['b']['c'] or "default" # => "hello"
Mortice
October 8, 2009, October 08, 2009 09:03, permalink
Always the way isn't it? I've found the neat trick.
#change create_hash to...
def create_hash
Hash.new do |hash, key|
hash[key] = hash.dup
end
end
#and you get as many levels of nesting as you like.
Mortice
October 8, 2009, October 08, 2009 09:23, permalink
Bah, using Hash#dup messes up the default value. To get that back, you need the below. Otherwise you always get an empty hash.
Apologies for the triple refactoring - only just managed to get logged in.
class Hash
def or(val)
val if self.empty?
end
end
# now the below works as expected
h = Hash.new {|h,k| h[k] = h.dup}
h['a']['b']['c'].or "default"
# but this returns h.dup...
h['a']['b']['c'] or "default"
rainchen
October 8, 2009, October 08, 2009 10:40, permalink
Mortice:
No need to extend Hash, use the "first" method is OK:
irb
irb(main):001:0> h = Hash.new {|h,k| h[k] = h.dup}
=> {}
irb(main):002:0> h['a']['b']['c'].first || "default"
=> "default"
irb(main):003:0> h['a']['b']['d'] = 'hi'
=> "hi"
irb(main):004:0> h['a']['b']['d'].first || "default"
=> "hi"
Adam
October 9, 2009, October 09, 2009 00:57, permalink
@Mortice dup-ing the hash copies the values that already exist in the hash leading to values that should not exist.
default_proc = lambda { |hash,key| hash[key] = Hash.new(&default_proc) }
hash = Hash.new(&default_proc)
Adam
October 9, 2009, October 09, 2009 04:26, permalink
And a convoluted solution that supports the || operator.
class MagicHash < Hash
module NilExtension
def __set_magic_hash__(hash, key)
@hash = hash[key] = MagicHash.new
end
def [](key)
if @hash
@hash = @hash[key] = MagicHash.new
nil
else
raise NoMethodError, "undefined method `[]' for nil:NilClass"
end
end
def []=(key, value)
if @hash
@hash[key] = value
else
raise NoMethodError, "undefined method `[]=' for nil:NilClass"
end
end
end
def [](key)
nil.__set_magic_hash__(self, key)
nil
end
NilClass.send(:include, NilExtension)
end
hash = MagicHash.new
hash[1][2][3] = true
=> {1=>{2=>{3=>true}}}
hash[1][2][3] || 'Foo'
=> true
hash[0][1][2] || 'Foo'
=> 'Foo'
mainej
October 9, 2009, October 09, 2009 05:35, permalink
Here's a little refactoring that passes all the original tests. It depends on the values responding to `empty?`, so it works with strings and hashes, but not much else.
module Lang
class Hash < ::Hash
def initialize
super { |h,k| h[k] = Lang::Hash.new }
end
def get
self
end
def [](k)
v = super
def v.or(other)
empty? ? other : self
end
v
end
end
end
mainej
October 9, 2009, October 09, 2009 05:40, permalink
Here's another refactoring: `get` has become trivial, so remove it. Removing a method changes the class' interface, but in a good way.
module Lang
class Hash < ::Hash
def initialize
super { |h,k| h[k] = Lang::Hash.new }
end
def [](k)
v = super
def v.or(other)
empty? ? other : self
end
v
end
end
end
require 'test/unit'
class HashTest < Test::Unit::TestCase
include Lang
def test_hash_nil_value_or_returns_other
h = Hash.new
val = h['nonexistant']
assert_equal 'val-was-false', (val.or 'val-was-false')
end
def test_hash_value_or_returns_value
h = Hash.new
h['a'] = 'a value'
val = h['a']
assert_equal 'a value', (val.or 'b value')
end
def test_hash_value_can_set_unset_stuff
h = Hash.new
h['a'] = 'value'
assert_equal 'value', h['a'].or('nil get')
assert_equal 'value', h['a']
end
def test_hash_value_can_set_unset_stuff_nested
h = Hash.new
h['a']['b'] = 'value'
assert_equal 'value', h['a']['b']
assert_equal 'value', h['a']['b'].or('nil get')
end
def test_hash_value_can_set_unset_stuff_more_nested
h = Hash.new
h['a']['b']['c'] = 'value'
assert_equal 'value', h['a']['b']['c']
assert_equal 'value', h['a']['b']['c'].or('nil get')
end
end
Yesterday i wrote this neat little hash so I don't can do something like myhash['a']['b']['c'] || default_vlaue. (Even when there is no h['a'] in the first place).
I thought it would be great example for refactoring. It could be made into one class, or just clean up/simplify the logic.
I would really like to see what you do to tihs code!