compare array of hashes and print expected & actual results - ruby

I have 2 array of hashes:
actual = [{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"},
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}]
expected = [{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"NUMBER"},
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"NUMBER"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}]
I need to compare these 2 hashes and find out the ones for which the column_data_type differs.
to compare we can directly use:
diff = actual - expected
This will print the output as:
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"}
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"}
My expected output is that in the result i want to print the actual and expected datatype, means the datatypes for the missing `column_name' from both the actual and expected array of hashes, something like:
{"column_name"=>"NONINTERESTEXPENSE", "expected_column_data_type"=>"NUMBER", "actual_column_data_type" => "VARCHAR"}
{"column_name"=>"TRANSACTIONDATE", "expected_column_data_type"=>"NUMBER","actual_column_data_type" => "TIMESTAMP" }

This will work irrespective of order of hashes in your array.
diff = []
expected.each do |elem|
column_name = elem['column_name']
column_type = elem['column_data_type']
match = actual.detect { |elem2| elem2['column_name'] == column_name }
if column_type != match['column_data_type']
diff << { 'column_name' => column_name,
'expected_column_data_type' => column_type,
'actual_column_data_type' => match['column_data_type'] }
end
end
p diff

[actual, expected].map { |a| a.map(&:dup).map(&:values) }
.map(&Hash.method(:[]))
.reduce do |actual, expected|
actual.merge(expected) do |k, o, n|
o == n ? nil : {name: k, actual: o, expected: n}
end
end.values.compact
#⇒ [
# [0] {
# :name => "NONINTERESTEXPENSE",
# :actual => "VARCHAR",
# :expected => "NUMBER"
# },
# [1] {
# :name => "TRANSACTIONDATE",
# :actual => "TIMESTAMP",
# :expected => "NUMBER"
# }
# ]
The method above easily expandable to merge N arrays (use reduce.with_index and merge with key "value_from_#{idx}".)

(expected - actual).
concat(actual - expected).
group_by { |column| column['column_name'] }.
map do |name, (expected, actual)|
{
'column_name' => name,
'expected_column_data_type' => expected['column_data_type'],
'actual_column_data_type' => actual['column_data_type'],
}
end

What about this?
def select(hashes_array, column_name)
hashes_array.select { |h| h["column_name"] == column_name }.first
end
diff = (expected - actual).map do |h|
{
"column_name" => h["column_name"],
"expected_column_data_type" => select(expected, h["column_name"])["column_data_type"],
"actual_column_data_type" => select(actual, h["column_name"])["column_data_type"],
}
end
PS: surely this code can be improved to look more elegant

Code
def convert(actual, expected)
hashify(actual-expected, "actual_data_type").
merge(hashify(expected-actual, "expected_data_type")) { |_,a,e| a.merge(e) }.values
end
def hashify(arr, key)
arr.each_with_object({}) { |g,h| h[g["column_name"]] =
{ "column_name"=>g["column_name"], key=>g["column_data_type"] } }
end
Example
actual = [
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"},
{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}
]
expected = [
{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"NUMBER"},
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"NUMBER"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}
]
convert(actual, expected)
#=> [{"column_name"=>"TRANSACTIONDATE",
# "actual_data_type"=>"TIMESTAMP", "expected_data_type"=>"NUMBER"},
# {"column_name"=>"NONINTERESTEXPENSE",
# "actual_data_type"=>"VARCHAR", "expected_data_type"=>"NUMBER"}]
Explanation
For the example above the steps are as follows.
First hashify actual and expected.
f = actual-expected
#=> [{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"},
# {"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"}]
g = hashify(f, "actual_data_type")
#=> {"TRANSACTIONDATE"=>{"column_name"=>"TRANSACTIONDATE",
# "actual_data_type"=>"TIMESTAMP"},
# "NONINTERESTEXPENSE"=>{ "column_name"=>"NONINTERESTEXPENSE",
# "actual_data_type"=>"VARCHAR"}}
h = expected-actual
#=> [{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"NUMBER"},
# {"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"NUMBER"}]
i = hashify(h, "expected_data_type")
#=> {"NONINTERESTEXPENSE"=>{"column_name"=>"NONINTERESTEXPENSE",
# "expected_data_type"=>"NUMBER"},
# "TRANSACTIONDATE"=>{"column_name"=>"TRANSACTIONDATE",
# "expected_data_type"=>"NUMBER"}}
Next merge g and i using the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged. See the doc for the definitions of the three block variables (the first of which, the common key, I've represented by an underscore to signify that it is not used in the block calculation).
j = g.merge(i) { |_,a,e| a.merge(e) }
#=> {"TRANSACTIONDATE"=>{"column_name"=>"TRANSACTIONDATE",
# "actual_data_type"=>"TIMESTAMP", "expected_data_type"=>"NUMBER"},
# "NONINTERESTEXPENSE"=>{"column_name"=>"NONINTERESTEXPENSE",
# "actual_data_type"=>"VARCHAR", "expected_data_type"=>"NUMBER"}}
Lastly, drop the keys.
k = j.values
#=> [{"column_name"=>"TRANSACTIONDATE", "actual_data_type"=>"TIMESTAMP",
# "expected_data_type"=>"NUMBER"},
# {"column_name"=>"NONINTERESTEXPENSE", "actual_data_type"=>"VARCHAR",
# "expected_data_type"=>"NUMBER"}]

Related

How to find the largest value of a hash in an array of hashes

In my array, I'm trying to retrieve the key with the largest value of "value_2", so in this case, "B":
myArray = [
"A" => {
"value_1" => 30,
"value_2" => 240
},
"B" => {
"value_1" => 40,
"value_2" => 250
},
"C" => {
"value_1" => 18,
"value_2" => 60
}
]
myArray.each do |array_hash|
array_hash.each do |key, value|
if value["value_2"] == array_hash.values.max
puts key
end
end
end
I get the error:
"comparison of Hash with Hash failed (ArgumentError)".
What am I missing?
Though equivalent, the array given in the question is generally written:
arr = [{ "A" => { "value_1" => 30, "value_2" => 240 } },
{ "B" => { "value_1" => 40, "value_2" => 250 } },
{ "C" => { "value_1" => 18, "value_2" => 60 } }]
We can find the desired key as follows:
arr.max_by { |h| h.values.first["value_2"] }.keys.first
#=> "B"
See Enumerable#max_by. The steps are:
g = arr.max_by { |h| h.values.first["value_2"] }
#=> {"B"=>{"value_1"=>40, "value_2"=>250}}
a = g.keys
#=> ["B"]
a.first
#=> "B"
In calculating g, for
h = arr[0]
#=> {"A"=>{"value_1"=>30, "value_2"=>240}}
the block calculation is
a = h.values
#=> [{"value_1"=>30, "value_2"=>240}]
b = a.first
#=> {"value_1"=>30, "value_2"=>240}
b["value_2"]
#=> 240
Suppose now arr is as follows:
arr << { "D" => { "value_1" => 23, "value_2" => 250 } }
#=> [{"A"=>{"value_1"=>30, "value_2"=>240}},
# {"B"=>{"value_1"=>40, "value_2"=>250}},
# {"C"=>{"value_1"=>18, "value_2"=>60}},
# {"D"=>{"value_1"=>23, "value_2"=>250}}]
and we wish to return an array of all keys for which the value of "value_2" is maximum (["B", "D"]). We can obtain that as follows.
max_val = arr.map { |h| h.values.first["value_2"] }.max
#=> 250
arr.select { |h| h.values.first["value_2"] == max_val }.flat_map(&:keys)
#=> ["B", "D"]
flat_map(&:keys) is shorthand for:
flat_map { |h| h.keys }
which returns the same array as:
map { |h| h.keys.first }
See Enumerable#flat_map.
Code
p myArray.pop.max_by{|k,v|v["value_2"]}.first
Output
"B"
I'd use:
my_array = [
"A" => {
"value_1" => 30,
"value_2" => 240
},
"B" => {
"value_1" => 40,
"value_2" => 250
},
"C" => {
"value_1" => 18,
"value_2" => 60
}
]
h = Hash[*my_array]
# => {"A"=>{"value_1"=>30, "value_2"=>240},
# "B"=>{"value_1"=>40, "value_2"=>250},
# "C"=>{"value_1"=>18, "value_2"=>60}}
k = h.max_by { |k, v| v['value_2'] }.first # => "B"
Hash[*my_array] takes the array of hashes and turns it into a single hash. Then max_by will iterate each key/value pair, returning an array containing the key value "B" and the sub-hash, making it easy to grab the key using first:
k = h.max_by { |k, v| v['value_2'] } # => ["B", {"value_1"=>40, "value_2"=>250}]
I guess the idea of your solution is looping through each hash element and compare the found minimum value with hash["value_2"].
But you are getting an error at
if value["value_2"] == array_hash.values.max
Because the array_hash.values is still a hash
{"A"=>{"value_1"=>30, "value_2"=>240}}.values.max
#=> {"value_1"=>30, "value_2"=>240}
It should be like this:
max = nil
max_key = ""
myArray.each do |array_hash|
array_hash.each do |key, value|
if max.nil? || value.values.max > max
max = value.values.max
max_key = key
end
end
end
# max_key #=> "B"
Another solution:
myArray.map{ |h| h.transform_values{ |v| v["value_2"] } }.max_by{ |k| k.values }.keys.first
You asked "What am I missing?".
I think you are missing a proper understanding of the data structures that you are using. I suggest that you try printing the data structures and take a careful look at the results.
The simplest way is p myArray which gives:
[{"A"=>{"value_1"=>30, "value_2"=>240}, "B"=>{"value_1"=>40, "value_2"=>250}, "C"=>{"value_1"=>18, "value_2"=>60}}]
You can get prettier results using pp:
require 'pp'
pp myArray
yields:
[{"A"=>{"value_1"=>30, "value_2"=>240},
"B"=>{"value_1"=>40, "value_2"=>250},
"C"=>{"value_1"=>18, "value_2"=>60}}]
This helps you to see that myArray has only one element, a Hash.
You could also look at the expression array_hash.values.max inside the loop:
myArray.each do |array_hash|
p array_hash.values
end
gives:
[{"value_1"=>30, "value_2"=>240}, {"value_1"=>40, "value_2"=>250}, {"value_1"=>18, "value_2"=>60}]
Not what you expected? :-)
Given this, what would you expect to be returned by array_hash.values.max in the above loop?
Use p and/or pp liberally in your ruby code to help understand what's going on.

Questions on implementing hashes in ruby

I'm new to ruby, I am solving a problem that involves hashes and key. The problem asks me to Implement a method, #pet_types, that accepts a hash as an argument. The hash uses people's # names as keys, and the values are arrays of pet types that the person owns. My question is about using Hash#each method to iterate through each num inside the array. I was wondering if there's any difference between solving the problem using hash#each or hash.sort.each?
I spent several hours coming up different solution and still to figure out what are the different approaches between the 2 ways of solving the problem below.
I include my code in repl.it: https://repl.it/H0xp/6 or you can see below:
# Pet Types
# ------------------------------------------------------------------------------
# Implement a method, #pet_types, that accepts a hash as an argument. The hash uses people's
# names as keys, and the values are arrays of pet types that the person owns.
# Example input:
# {
# "yi" => ["dog", "cat"],
# "cai" => ["dog", "cat", "mouse"],
# "venus" => ["mouse", "pterodactyl", "chinchilla", "cat"]
# }
def pet_types(owners_hash)
results = Hash.new {|h, k| h[k] = [ ] }
owners_hash.sort.each { |k, v| v.each { |pet| results[pet] << k } }
results
end
puts "-------Pet Types-------"
owners_1 = {
"yi" => ["cat"]
}
output_1 = {
"cat" => ["yi"]
}
owners_2 = {
"yi" => ["cat", "dog"]
}
output_2 = {
"cat" => ["yi"],
"dog" => ["yi"]
}
owners_3 = {
"yi" => ["dog", "cat"],
"cai" => ["dog", "cat", "mouse"],
"venus" => ["mouse", "pterodactyl", "chinchilla", "cat"]
}
output_3 = {
"dog" => ["cai", "yi"],
"cat" => ["cai", "venus", "yi"],
"mouse" => ["cai", "venus"],
"pterodactyl" => ["venus"],
"chinchilla" => ["venus"]
}
# method 2
# The 2nd and 3rd method should return a hash that uses the pet types as keys and the values should
# be a list of the people that own that pet type. The names in the output hash should
# be sorted alphabetically
# switched_hash = Hash.new()
# owners_hash.each do |owner, pets_array|
# pets_array.each do |pet|
# select_owners = owners_hash.select { |owner, pets_array|
owners_hash[owner].include?(pet) }
# switched_hash[pet] = select_owners.keys.sort
# end
# end
# method 3
#switched_hash
# pets = Hash.new {|h, k| h[k] = [ ] } # WORKS SAME AS: pets = Hash.new( Array.new )
# owners = owners_hash.keys.sort
# owners.each do |owner|
# owners_hash[owner].each do |pet|
# pets[pet] << owner
# end
# end
# pets
# Example output:
# output_3 = {
# "dog" => ["cai", "yi"],
# "cat" => ["cai", "venus", "yi"], ---> (sorted alphabetically!)
# "mouse" => ["cai", "venus"],
# "pterodactyl" => ["venus"],
# "chinchilla" => ["venus"]
# }
I used a hash data structure in my program to first solve this problem. Then I tried to rewrite it using the pet_hash. And my final codes is the following:
def pet_types(owners_hash)
pets_hash = Hash.new { |k, v| v = [] }
owners_hash.each do |owner, pets|
pets.each do |pet|
pets_hash[pet] += [owner]
end
end
pets_hash.values.each(&:sort!)
pets_hash
end
puts "-------Pet Types-------"
owners_1 = {
"yi" => ["cat"]
}
output_1 = {
"cat" => ["yi"]
}
owners_2 = {
"yi" => ["cat", "dog"]
}
output_2 = {
"cat" => ["yi"],
"dog" => ["yi"]
}
owners_3 = {
"yi" => ["dog", "cat"],
"cai" => ["dog", "cat", "mouse"],
"venus" => ["mouse", "pterodactyl", "chinchilla", "cat"]
}
output_3 = {
"dog" => ["cai", "yi"],
"cat" => ["cai", "venus", "yi"],
"mouse" => ["cai", "venus"],
"pterodactyl" => ["venus"],
"chinchilla" => ["venus"]
}
puts pet_types(owners_1) == output_1
puts pet_types(owners_2) == output_2
puts pet_types(owners_3) == output_3
Hash#sort has the same effect (at least for my basic test) as Hash#to_a followed by Array#sort.
hash = {b: 2, a: 1}
hash.to_a.sort # => [[:a, 1, [:b, 2]]
hash.sort # => the same
Now let's look at #each, both on Hash and Array.
When you provide two arguments to the block, that can handle both cases. For the hash, the first argument will be the key and the second will be the value. For the nested array, the values essentially get splatted out to the args:
[[:a, 1, 2], [:b, 3, 4]].each { |x, y, z| puts "#{x}-#{y}-#{z}" }
# => a-1-2
# => b-3-4
So basically, you should think of Hash#sort to be a shortcut to Hash#to_a followed by Array#sort, and recognize that #each will work the same on a hash as a hash converted to array (a nested array). In this case, it doesn't matter which approach you take. Clearly if you need to sort iteration by the keys then you should use sort.

How can I delete duplicate values from a hash of hash

I need a unique hash , where the value of "one" should never repeat.
for example,
hash= {"1"=>{"one"=>1,"two"=>2},
"2"=>{"one"=>1,"two"=>3},
"3"=>{"one"=>2,"two"=>3},
"4"=>{"one"=>1,"two"=>2}}
then the result should be,
hash= {"1"=>{"one"=>1,"two"=>2},
"3"=>{"one"=>2,"two"=>3}}
(readable) one-liner:
hash.to_a.uniq {|(_,v)| v['one']}.to_h
# {"1"=>{"one"=>1, "two"=>2}, "3"=>{"one"=>2, "two"=>3}}
ones_values = {}
hash.delete_if do |key, value|
ones_values[value["one"]] ? true : (ones_values[value["one"]] = true) && false
end
hash.inject({}) do |memo, (k, v)|
memo[k] = v unless memo.values.any? do |mv|
mv['one'] == v['one']
end
memo
end
#⇒ {
# "1" => {
# "one" => 1,
# "two" => 2
# },
# "3" => {
# "one" => 2,
# "two" => 3
# }
# }

Consolidate nested arrays and erase the subarrays that have been consolidated?

I'm trying to take a bunch of number-word pairs and group the words according to common numbers. I can match the numbers, merge the subarrays that share the number, and erase the first of those subarrays. But when I try to delete the second, I get this error:
"in block in <main>': undefined method[]' for nil:NilClass (NoMethodError)"
The guilty line -- ary.delete_at(i+1) -- has been commented out. Secondary problem: MRBTree is not taking the nested arrays as input...
ary = [[2.28, "cat"], [2.28, "bat"], [2.327, "bear"], [2.68, "ant"], [2.68, "anu"]]
i = 0
for i in 0 ... ary.size - 1
if ary[i][0] == ary[i+1][0]
b = (ary[i]+ary[i+1]).uniq
ary.delete_at(i)
# ary.delete_at(i+1)
c = [b.first], b.pop(b.length - 1)
h = Hash[*c]
ary.push(*h)
# mrbtree = MultiRBTree[c]
end
end
puts ary.inspect
output:
# => [
# => [2.28, "bat"],
# => [2.327, "bear"],
# => [2.68, "anu"],
# => [
# => [2.28], ["cat", "bat"]
# => ],
# => [
# => [2.68], ["ant", "anu"]
# => ]
# => ]
Any help appreciated!
Your attempt is failing because you are modifying the array (which has impact on a.size) in the loop. The loop end condition is not adjusted automagically. You are accessing things you have deleted before.
If your array is not too big, this will do:
p Hash[ary.group_by(&:first).map { | k, v | [k, v.map(&:last)] }]
# => {2.28=>["cat", "bat"], 2.327=>["bear"], 2.68=>["ant", "anu"]}
It works this way:
ary.group_by(&:first) # group the 2-elem arrays by the number, creating a hash
# like {2.28=>[[2.28, "cat"], [2.28, "bat"]], ...}
.map { | k, v | ... } # change the array of key-value pairs to
[k, v.map(&:last)] # pairs where the value-array contains just the strings
Hash[ ... ] # make the whole thing a hash again
Creating an intermediate array and transferring it back to a hash is some overhead. If this turns out to be an issue, something like this might be better:
h = Hash.new { | a, k | a[k] = [] } # a hash with [] as default value
p ary.inject(h) { | a, (k, v) | a[k] << v; a }
It looks like after
ary.delete_at(i)
the size of array is decreased by one, hence i is better than i+1:
# ary.delete_at(i+1)
ary.delete_at(i)
Alternate version for converting to hash:
ary = [[2.28, "cat"], [2.28, "bat"], [2.327, "bear"], [2.68, "ant"], [2.68, "anu"]]
hsh = {}
ary.each {|pair| hsh[pair[0]].nil? ? hsh[pair[0]] = [pair[1]] : hsh[pair[0]] << pair[1]}
puts hsh.inspect # => {2.28 => ["cat", "bat"], 2.327 => ["bear"], 2.68 => ["ant", "anu"]}

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

Resources