Accessing nested hashes using variables - ruby

I have a nested hash like so:
someVar = { key1: { key2: 'value' } }
I can access the value by using it in this manner:
someVar[:key1][:key2]
How would I access it using a variable?
hashObj = { key1: { key2: 'value' } }
oneKey = "key1"
twoKey = "key2"
puts hashObj[:key1] # Works
puts hashObj[:key1][:key2] # Works
puts hashObj[oneKey] # Blank
puts hashObj[oneKey][twoKey] # Error
I'm sure there is a duplicate of this question somewhere, but I can't seem to locate one however.

Your keys are symbols, and you are trying to accessing them using strings. Turn them into symbols:
puts hashObj[oneKey.to_sym][twoKey.to_sym]

You might find it convenient to write a small method to extract the values you want:
def get_val(h, *keys)
keys.reduce(h) do |h,k|
v = h[k]
return v unless v.is_a? Hash
v
end
end
h = { key1: { key2: 'cat' }, key3: { key4: { key5: 'dog' } } }
get_val(h, :key1, :key2) #=> "cat"
get_val(h, :key3, :key4, :key5) #=> "dog"
Some error-checking would be needed, should, for example,
get_val(h, :key1, :key2, :key3)
is entered.
Edit: With Ruby 2.3+ you can improve this by using Hash#dig:
def get_val(h, *keys)
h.dig *keys
end
get_val(h, :key3, :key4, :key5)
#=> "dog"
get_val(h, :key3, :key4)
#=> {:key5=>"dog"}
get_val(h, :key3, :key4, :key5)
#=> "dog"
get_val(h, :key3, :key5, :key4)
#=> nil

Related

how does reduce or inject work in this code

Found this on code wars as one of the solutions. Can someone explain to me how "args.reduce(self)" works in this code; the block after makes sense.
config = { :files => { :mode => 0x777 }, :name => "config" }
class Hash
def get_value( default, *args )
args.empty? ? default : args.reduce(self) { |acum, key| acum.fetch(key) } rescue default
end
end
config.get_value("", :files,:mode)
Suppose we execute
{ :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b, :c)
so that within the method
default #=> 4
args #=> [:a, :b, :c]
self #=> { :a=>{:b=>{:c=>3 } } }
We then execute the following1:
args.empty? ? default : args.reduce(self) { |acum, key| acum.fetch(key) } rescue default
#=> [:a, :b, :c].empty? ? 4 : [:a, :b, :c].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
# acum.fetch(key) } rescue 4
#=> 3
If args #=> [:a, :b], we execute the following:
[:a, :b].empty? ? 4 : [:a, :b].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
acum.fetch(key) } rescue 4
#=> {:c=>3}
If args #=> [:a, :b, :cat], then a KeyError exception is raised and the inline rescue returns the value of default:
[:a, :b, :cat].empty? ? 4 : [:a, :b, :cat].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
acum.fetch(key) } rescue 4
#=> 4
and if args #=> [], [].empty? is true, so the value of default is again returned:
[].empty? ? 4 : [].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
acum.fetch(key) } rescue 4
#=> 4
Fortunately, we no longer have to deal with such nonsense as we were given Hash#dig in Ruby 2.3.0, allowing us to write the following.
class Hash
def get_value( default, *keys )
keys.empty? ? default : dig(*keys) || default
end
end
{ :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b, :c)
#=> 3
{ :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b)
#=> {:c=>3}
{ :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b, :cat)
#=> 4
{ :a=>{:b=>{:c=>3 } } }.get_value(4)
#=> 4
Note that the default receiver of dig is self.
1 Note that instead of ...args.reduce(self) { |acum, key| acum.fetch(key) } rescue default the author of that code could have written ...args.reduce(self) { |acum, key| acum.fetch(key, default) }. See Hash#fetch.
It assumes self is a nest of hashes, and treats args as a sequence of keys to dive deeper and deeper into that nest of hashes.
self is the config hash itself.
reduce accepts an argument which is self (hence original config hash).
Then accum on first iteration will be assigned with that argument (which is original config hash).
On each iteration, the accum will be reassigned with the (nested) value for each key of args.
Let's look at the following example instead.
config = {
:files => { :mode => 0x777 },
:name => "config"
}
[:files, :mode].reduce(config) { |hash, key|
# The value for the current key, which can be another hash.
newhash = hash.fetch(key)
# Log each iteration here to see what's happening.
# p newhash
# Return the value for next iteration.
newhash
}
The output is the HEX value 0x777, which is converted by Ruby to the decimal 1911.
In your example with args.reduce(self), self is the initial value that is passed as the first argument to the block, which is the config hash itself. Array mixes in Enumerable, and that's where reduce comes from. More info here: http://ruby-doc.org/core-1.9.3/Enumerable.html#method-i-reduce
For each iteration of reduce, the block variables contain the following values:
Iteration 1
hash contains { :files => { :mode => 0x777 }, :name => "config" }; this is the config hash itself.
key contains :files; the first item of the array.
newhash contains {:mode=>1911}, which we return and becomes the first argument of the next iteration.
Iteration 2
hash contains {:mode=>1911} because we returned newhash in the previous iteration.
key contains :mode; the second item of the array.
newhash contains 1911; the reduce iterations are done and this is the final value.

How to convert, in Ruby, a list of key value pairs to a Hash, such that values with duplicate keys are stored in an array?

Given a list key-value pairs, in the form of an array of arrays - e.g. [ ["key1","value1"], ["key2","value2"], ["key1", "value3"] ], how to convert these to a Hash that stores all the values, in the most elegant way?
For the above example, I would want to get { "key1" => [ "value1", "value3" ], "key2" => [ "value2" ] }.
[["key1","value1"], ["key2","value2"], ["key1", "value3"]]
.group_by(&:first).each{|_, v| v.map!(&:last)}
Another way is to use the form of Hash#update (aka merge!) that uses a block to determine the values of keys that are in both hashes being merged.
arr = [ ["key1","value1"], ["key2","value2"], ["key1", "value3"] ]
arr.each_with_object({}) { |(k,v),h| h.update(k=>[v]) { |_,o,n| o+n } }
#=> {"key1"=>["value1", "value3"], "key2"=>["value2"]}
Hash.new{ |h,k| h[k]=[] }.tap{ |h| array.each{ |k,v| h[k] << v } }
OR
c = Hash.new {|h,k| h[k] = [] }
array.each{|k, v| c[k] << v}
My best solution so far is this:
kvlist.inject(Hash.new([])) do |memo,a|
memo[a[0]] = (memo[a[0]] << a[1])
memo
end
Which I think is not very good.

How do you replace a string with a hash collection value in Ruby?

I have a hash collection:
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
What syntax would I use to replace the first occurrence of the key with hash collection value in a string?
eg my input string:
str = I want a 3
The resulting string would be:
str = I want a cat
My one liner:
hash.each { |k, v| str[k] &&= v }
or using String#sub! method:
hash.each { |k, v| str.sub!(k, v) }
"I want a %{b}" % {c: "apple", b: "bee", a: "cat"}
=> "I want a bee"
Assuming Ruby 1.9 or later:
str.gsub /\d/, my_hash
I didn't understand your problem, but you can try this:
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
str = "I want a 3"
str.gsub(/[[:word:]]+/).each do |word|
my_hash[word] || word
end
#=> "I want a cat"
:D
Just to add point free style abuse to fl00r's answer:
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
my_hash.default_proc = Proc.new {|hash, key| key}
str = "I want a 3"
str.gsub(/[[:word:]]+/).each(&my_hash.method(:[]))
my_hash = {"1" => "apple", "2" => "bee", "3" => "cat"}
str = "I want a 3"
If there isn't any general pattern for the strings you want to substitute, you can use:
str.sub /#{my_hash.keys.map { |s| Regexp.escape s }.join '|'}/, my_hash
But if there is one, the code becomes much simpler, e.g.:
str.sub /[0-9]+/, my_hash
If you want to substitute all the occurrences, not only the first one, use gsub.
You can use String.sub in ruby 1.9:
string.sub(key, hash[key])
The following code replace the first occurrence of the key with hash collection value in the given string str
str.gsub(/\w+/) { |m| my_hash.fetch(m,m)}
=> "I want a cat"

How do I convert a Ruby hash so that all of its keys are symbols?

I have a Ruby hash which looks like:
{ "id" => "123", "name" => "test" }
I would like to convert it to:
{ :id => "123", :name => "test" }
hash = {"apple" => "banana", "coconut" => "domino"}
Hash[hash.map{ |k, v| [k.to_sym, v] }]
#=> {:apple=>"banana", :coconut=>"domino"}
#mu is too short: Didn't see word "recursive", but if you insist (along with protection against non-existent to_sym, just want to remind that in Ruby 1.8 1.to_sym == nil, so playing with some key types can be misleading):
hash = {"a" => {"b" => "c"}, "d" => "e", Object.new => "g"}
s2s =
lambda do |h|
Hash === h ?
Hash[
h.map do |k, v|
[k.respond_to?(:to_sym) ? k.to_sym : k, s2s[v]]
end
] : h
end
s2s[hash] #=> {:d=>"e", #<Object:0x100396ee8>=>"g", :a=>{:b=>"c"}}
If you happen to be in Rails then you'll have symbolize_keys:
Return a new hash with all keys converted to symbols, as long as they respond to to_sym.
and symbolize_keys! which does the same but operates in-place. So, if you're in Rails, you could:
hash.symbolize_keys!
If you want to recursively symbolize inner hashes then I think you'd have to do it yourself but with something like this:
def symbolize_keys_deep!(h)
h.keys.each do |k|
ks = k.to_sym
h[ks] = h.delete k
symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
end
end
You might want to play with the kind_of? Hash to match your specific circumstances; using respond_to? :keys might make more sense. And if you want to allow for keys that don't understand to_sym, then:
def symbolize_keys_deep!(h)
h.keys.each do |k|
ks = k.respond_to?(:to_sym) ? k.to_sym : k
h[ks] = h.delete k # Preserve order even when k == ks
symbolize_keys_deep! h[ks] if h[ks].kind_of? Hash
end
end
Note that h[ks] = h.delete k doesn't change the content of the Hash when k == ks but it will preserve the order when you're using Ruby 1.9+. You could also use the [(key.to_sym rescue key) || key] approach that Rails uses in their symbolize_keys! but I think that's an abuse of the exception handling system.
The second symbolize_keys_deep! turns this:
{ 'a' => 'b', 'c' => { 'd' => { 'e' => 'f' }, 'g' => 'h' }, ['i'] => 'j' }
into this:
{ :a => 'b', :c => { :d => { :e => 'f' }, :g => 'h' }, ['i'] => 'j' }
You could monkey patch either version of symbolize_keys_deep! into Hash if you really wanted to but I generally stay away from monkey patching unless I have very good reasons to do it.
If you are using Rails >= 4 you can use:
hash.deep_symbolize_keys
hash.deep_symbolize_keys!
or
hash.deep_stringify_keys
hash.deep_stringify_keys!
see http://apidock.com/rails/v4.2.1/Hash/deep_symbolize_keys
Just in case you are parsing JSON, from the JSON docs you can add the option to symbolize the keys upon parsing:
hash = JSON.parse(json_data, symbolize_names: true)
Victor Moroz provided a lovely answer for the simple recursive case, but it won't process hashes that are nested within nested arrays:
hash = { "a" => [{ "b" => "c" }] }
s2s[hash] #=> {:a=>[{"b"=>"c"}]}
If you need to support hashes within arrays within hashes, you'll want something more like this:
def recursive_symbolize_keys(h)
case h
when Hash
Hash[
h.map do |k, v|
[ k.respond_to?(:to_sym) ? k.to_sym : k, recursive_symbolize_keys(v) ]
end
]
when Enumerable
h.map { |v| recursive_symbolize_keys(v) }
else
h
end
end
Try this:
hash = {"apple" => "banana", "coconut" => "domino"}
# => {"apple"=>"banana", "coconut"=>"domino"}
hash.tap do |h|
h.keys.each { |k| h[k.to_sym] = h.delete(k) }
end
# => {:apple=>"banana", :coconut=>"domino"}
This iterates over the keys, and for each one, it deletes the stringified key and assigns its value to the symbolized key.
If you're using Rails (or just Active Support):
{ "id" => "123", "name" => "test" }.symbolize_keys
Starting with Ruby 2.5 you can use the transform_key method.
So in your case it would be:
h = { "id" => "123", "name" => "test" }
h.transform_keys!(&:to_sym) #=> {:id=>"123", :name=>"test"}
Note: the same methods are also available on Ruby on Rails.
Here's a Ruby one-liner that is faster than the chosen answer:
hash = {"apple" => "banana", "coconut" => "domino"}
#=> {"apple"=>"banana", "coconut"=>"domino"}
hash.inject({}){|h,(k,v)| h[k.intern] = v; h}
#=> {:apple=>"banana", :coconut=>"domino"}
Benchmark results:
n = 100000
Benchmark.bm do |bm|
bm.report { n.times { hash.inject({}){|h,(k,v)| h[k.intern] = v; h} } }
bm.report { n.times { Hash[hash.map{ |k, v| [k.to_sym, v] }] } }
end
# => user system total real
# => 0.100000 0.000000 0.100000 ( 0.107940)
# => 0.120000 0.010000 0.130000 ( 0.137966)
I'm partial to:
irb
ruby-1.9.2-p290 :001 > hash = {"apple" => "banana", "coconut" => "domino"}
{
"apple" => "banana",
"coconut" => "domino"
}
ruby-1.9.2-p290 :002 > hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
{
:apple => "banana",
:coconut => "domino"
}
This works because we're iterating over the hash and building a new one on the fly. It isn't recursive, but you could figure that out from looking at some of the other answers.
hash.inject({}){ |h, (n,v)| h[n.to_sym] = v; h }
You can also extend core Hash ruby class placing a /lib/hash.rb file :
class Hash
def symbolize_keys_deep!
new_hash = {}
keys.each do |k|
ks = k.respond_to?(:to_sym) ? k.to_sym : k
if values_at(k).first.kind_of? Hash or values_at(k).first.kind_of? Array
new_hash[ks] = values_at(k).first.send(:symbolize_keys_deep!)
else
new_hash[ks] = values_at(k).first
end
end
new_hash
end
end
If you want to make sure keys of any hash wrapped into arrays inside your parent hash are symbolized, you need to extend also array class creating a "array.rb" file with that code :
class Array
def symbolize_keys_deep!
new_ar = []
self.each do |value|
new_value = value
if value.is_a? Hash or value.is_a? Array
new_value = value.symbolize_keys_deep!
end
new_ar << new_value
end
new_ar
end
end
This allows to call "symbolize_keys_deep!" on any hash variable like this :
myhash.symbolize_keys_deep!
def symbolize_keys(hash)
new={}
hash.map do |key,value|
if value.is_a?(Hash)
value = symbolize_keys(value)
end
new[key.to_sym]=value
end
return new
end
puts symbolize_keys("c"=>{"a"=>2,"k"=>{"e"=>9}})
#{:c=>{:a=>2, :k=>{:e=>9}}}
Here's my two cents,
my version of symbolize_keys_deep! uses the original symbolize_keys! provided by rails and just makes a simple recursive call to Symbolize sub hashes.
def symbolize_keys_deep!(h)
h.symbolize_keys!
h.each do |k, v|
symbolize_keys_deep!(v) if v.is_a? Hash
end
end
Facets' Hash#rekey is also a worth mentioning.
Sample:
require 'facets/hash/rekey'
{ "id" => "123", "name" => "test" }.deep_rekey
=> {:id=>"123", :name=>"test"}
There is also a recursive version:
require 'facets/hash/deep_rekey'
{ "id" => "123", "name" => {"first" => "John", "last" => "Doe" } }.deep_rekey
=> {:id=>"123", :name=>{:first=>"John", :last=>"Doe"}}
Here's a little recursive function to do a deep symbolization of the keys:
def symbolize_keys(hash)
Hash[hash.map{|k,v| v.is_a?(Hash) ? [k.to_sym, symbolize_keys(v)] : [k.to_sym, v] }]
end

what's the best way to convert a ruby hash to an array

I have a ruby hash that looks like this
{ "stuff_attributes" => {
"1" => {"foo" => "bar", "baz" => "quux"},
"2" => {"foo" => "bar", "baz" => "quux"}
}
}
and I want to turn it into a hash that looks like this
{ "stuff_attributes" => [
{ "foo" => "bar", "baz" => "quux"},
{ "foo" => "bar", "baz" => "quux"}
]
}
I also need to preserve the numerical order of the keys, and there is a variable number of keys. The above is super-simplified, but I've included a real example at the bottom. What's the best way to do this?
P.S
It also needs to be recursive
As far as the recursion goes, here's what we can assume:
1) the key that needs to be manipulated will match /_attributes$/
2) the hash will have many other keys that do not match /_attributes$/
3) the keys inside the hash will always be a number
4) an _attributes hash can be at any level of the hash under any other key
this hash is actually the params hash from a create action in the controller. This is a real example of what will need to be parsed with this routine.
{
"commit"=>"Save",
"tdsheet"=>{
"team_id"=>"43",
"title"=>"",
"performing_org_id"=>"10",
"tdsinitneed_attributes"=>{
"0"=>{
"title"=>"",
"need_date"=>"",
"description"=>"",
"expected_providing_organization_id"=>"41"
},
"1"=>{
"title"=>"",
"need_date"=>"",
"description"=>"",
"expected_providing_organization_id"=>"41"
}
},
"level_two_studycollection_id"=>"27",
"plan_attributes"=>{
"0"=>{
"start_date"=>"", "end_date"=>""
}
},
"dataitem_attributes"=>{
"0"=>{
"title"=>"",
"description"=>"",
"plan_attributes"=>{
"0"=>{
"start_date"=>"",
"end_date"=>""
}
}
},
"1"=>{
"title"=>"",
"description"=>"",
"plan_attributes"=>{
"0"=>{
"start_date"=>"",
"end_date"=>""
}
}
}
}
},
"action"=>"create",
"studycollection_level"=>"",
"controller"=>"tdsheets"
}
Note that this might be long to test if all keys are numbers before converting...
def array_from_hash(h)
return h unless h.is_a? Hash
all_numbers = h.keys.all? { |k| k.to_i.to_s == k }
if all_numbers
h.keys.sort_by{ |k| k.to_i }.map{ |i| array_from_hash(h[i]) }
else
h.each do |k, v|
h[k] = array_from_hash(v)
end
end
end
If we can assume that all the keys are in fact strings which convert cleanly to integers, the following ought to work:
# "hash" here refers to the main hash in your example, since you didn't name it
stuff_hash = hash["stuff"]
hash["stuff"] = stuff_hash.keys.sort_by {|key| key.to_i}.map {|key| stuff_hash[key]}
To take a bit of a liberty, I'm posting a very similar code example to Vincent Robert's.
This one is patches the Hash class with a .to_array method.
class Hash
def to_array(h = self)
return h unless h.is_a? Hash
if h.keys.all? { |k| k.to_i.to_s == k } # all keys are numbers so make an array.
h.keys.sort_by{ |k| k.to_i }.map{ |i| self.to_array(h[i]) }
else
h.each do |k, v|
h[k] = self.to_array(v)
end
end
end
end
It makes usage slightly more convenient.

Resources