I am trying to merge two Hashes.I got the code from here. As you can see I want to add one more hobby to my list.
There are many hobbies. When h1 is formed ,only one hobby was available. When the second hobby arrived I wanted to merge it with the previous hash.
The structure of the Hashes are :
h1 = {"students"=>[{"name"=>"bobby", "hobbies"=>[{"outdoor"=>"cycling"}]}]}
h2 = {"students"=>[{"name"=>"bobby", "hobbies"=>[{"indoor"=>"reading"}]}]}
I ran this code:
res = h1.merge(h2) {|key,val1,val2| val1.merge val2}
The error I got was:
undefined method `merge' for [{"name"=>"bobby", "hobbies"=>[{"outdoor"=>"cycling"}]}]:Array (NoMethodError)
So since array is involved, I don't know what to do. Any Help.
Required Output:
{"students"=>[{"name"=>"bobby", "hobbies"=>[{"outdoor"=>"cycling", "indoor"=>"reading"}]}]}
The built-in marge method cannot do what you want.
You should write the original code to merge these structure like this.
def recursive_merge(h1, h2)
case h1
when Hash
(h1.keys + h2.keys).each do |key|
if h1.include?(key) && h2.include?(key) then
recursive_merge(h1[key], h2[key])
elsif h2.include?(key)
h1[key] = h2[key]
end
end
when Array
for i in 0...([h1.count, h2.count].max)
if i < h1.count && i < h2.count then
recursive_merge(h1[i], h2[i])
elsif i < h2.count then
h1[i] = h2[i]
end
end
end
end
recursive_merge(h1, h2)
puts h1
This method results the follwing output.
Please note that this method overwrites h1 itself.
{"students"=>[{"name"=>"bobby", "hobbies"=>[{"outdoor"=>"cycling", "indoor"=>"reading"}]}]}
The answer on your question might be:
h1.merge(h2) { |k, v1, v2|
v1.map.with_index { |v1e, idx|
v1e['hobbies'].first.merge! v2[idx]['hobbies'].first
v1e
}
v1
}
#⇒ {
# "students" => [
# [0] {
# "hobbies" => [
# [0] {
# "indoor" => "reading",
# "outdoor" => "cycling"
# }
# ],
# "name" => "bobby"
# }
# ]
# }
But I would definitely redesign hobbies to be a hash, not an array of hashes, eliminating weird first calls:
v1e['hobbies'].merge! v2[idx]['hobbies']
Hope it helps.
Related
If I want to recursively merge 2 hashes, I can do so with the following function:
def recursive_merge(a,b)
a.merge(b) {|key,a_item,b_item| recursive_merge(a_item,b_item) }
end
This works great, in that I can now do:
aHash = recursive_merge(aHash,newHash)
But I'd like to add this as a self-updating style method similar to merge!. I can add in the returning function:
class Hash
def recursive_merge(newHash)
self.merge { |key,a_item,b_item| a_item.recursive_merge(b_item) }
end
end
But am not sure how to re-create the bang function that updates the original object without association.
class Hash
def recursive_merge!(newHash)
self.merge { |key,a_item,b_item| a_item.recursive_merge(b_item) }
# How do I set "self" to this new hash?
end
end
edit example as per comments.
h={:a=>{:b => "1"}
h.recursive_merge!({:a=>{:c=>"2"})
=> {:a=>{:b=>"1", :c="2"}}
The regular merge results in :b=>"1" being overwritten by :c="2"
Use merge! rather than attempt to update self. I don't believe it makes sense to use merge! anywhere but at the top level, so I wouldn't call the bang version recursively. Instead, use merge! at the top level, and call the non-bang method recursively.
It may also be wise to check both values being merged are indeed hashes, otherwise you may get an exception if you attempt to recursive_merge on a non-hash object.
#!/usr/bin/env ruby
class Hash
def recursive_merge(other)
self.merge(other) { |key, value1, value2| value1.is_a?(Hash) && value2.is_a?(Hash) ? value1.recursive_merge(value2) : value2}
end
def recursive_merge!(other)
self.merge!(other) { |key, value1, value2| value1.is_a?(Hash) && value2.is_a?(Hash) ? value1.recursive_merge(value2) : value2}
end
end
h1 = { a: { b:1, c:2 }, d:1 }
h2 = { a: { b:2, d:4 }, d:2 }
h3 = { d: { b:1, c:2 } }
p h1.recursive_merge(h2) # => {:a=>{:b=>2, :c=>2, :d=>4}, :d=>2}
p h1.recursive_merge(h3) # => {:a=>{:b=>1, :c=>2}, :d=>{:b=>1, :c=>2}}
p h1.recursive_merge!(h2) # => {:a=>{:b=>2, :c=>2, :d=>4}, :d=>2}
p h1 # => {:a=>{:b=>2, :c=>2, :d=>4}, :d=>2}
If you have a specific reason to fully merge in place, possibly for speed, you can experiment with making the second function call itself recursively, rather than delegate the recursion to the first function. Be aware that may produce unintended side effects if the hashes store shared objects.
Example:
h1 = { a:1, b:2 }
h2 = { a:5, c:9 }
h3 = { a:h1, b:h2 }
h4 = { a:h2, c:h1 }
p h3.recursive_merge!(h4)
# Making recursive calls to recursive_merge
# => {:a=>{:a=>5, :b=>2, :c=>9}, :b=>{:a=>5, :c=>9}, :c=>{:a=>1, :b=>2}}
# Making recursive calls to recursive_merge!
# => {:a=>{:a=>5, :b=>2, :c=>9}, :b=>{:a=>5, :c=>9}, :c=>{:a=>5, :b=>2, :c=>9}}
As you can see, the second (shared) copy of h1 stored under the key :c is updated to reflect the merge of h1 and h2 under the key :a. This may be surprising and unwanted. Hence why I recommend using recursive_merge for the recursion, and not recursive_merge!.
I have a large array $actor_movie_array, which is sorted in this manner:
[actor1, movie1, movie2, movie3...]
[actor2, movie1, movie2, movie3...]
[actor3, movie1, movie2...]
The following method takes in a parameter actor_name, and then searches through $actor_movie_array for actor_name:
def search by actor(actor_name)
result_array = Array.new
$actor_movie_array.each {
|x| if x[0] == actor_name
result_array = x
result_array.delete_at(0)
break
end
}
return result_array
end
If found, it will be pushed into an empty array result_array that looks like this:
result_array = [actor1, movie1, movie2, movie3...]
Then, I will delete the first index of the array, which in this case is actor1, as I only want the remaining movies by this actor left in result_array.
However, this is really inefficient. I know that the hash equivalent would be more efficient, but do not know how to do it. Could anyone help to translate this into a hash equivalent?
OK, well, assuming you have a Hash of the form actors_to_movies = {actor1 => [movie1, movie2, movie3], actor2 => [movie4, movie5, movie6]}, you can just loop up lists of movies by actor the same way you'd look up anything in a Hash — for example actors_to_movies[actor1] would give [movie1, movie2, movie3].
If you're trying to figure out how to generate a hash from an array of the form you have, you'd do it like this:
actors_to_movies = Hash[ $actor_movie_array.map {|key, *vals| [key, vals] } ]
(Note that transforming a large array into a hashmap will take even longer than searching the array — but you'll ideally only have to do it once.)
I'd do as below
array_of_array = [ %w[actor1 movie1 movie2 movie3],
%w[actor2 movie1 movie2 movie3],
%w[actor3 movie1 movie2]
]
def search(ary,actor_name)
match = ary.find { |a| a.first == actor_name }
match.nil? ? "no actor found" : match[1..-1]
end
search(array_of_array,'actor2')
# => ["movie1", "movie2", "movie3"]
search(array_of_array,'actor5')
# => "no actor found"
Or, take the below approach :
array_of_array = [ %w[actor1 movie1 movie2 movie3],
%w[actor2 movie1 movie2 movie3],
%w[actor3 movie1 movie2]
]
hsh = Hash[ary.map { |key,*val| [key,val] }]
def search(hash,actor_name)
hash.fetch(actor_name,"no actor found")
end
search(hsh,'actor2')
# => ["movie1", "movie2"]
search(hsh,'actor5')
# => "no actor found"
I need to create a signature string for a variable in Ruby, where the variable can be a number, a string, a hash, or an array. The hash values and array elements can also be any of these types.
This string will be used to compare the values in a database (Mongo, in this case).
My first thought was to create an MD5 hash of a JSON encoded value, like so: (body is the variable referred to above)
def createsig(body)
Digest::MD5.hexdigest(JSON.generate(body))
end
This nearly works, but JSON.generate does not encode the keys of a hash in the same order each time, so createsig({:a=>'a',:b=>'b'}) does not always equal createsig({:b=>'b',:a=>'a'}).
What is the best way to create a signature string to fit this need?
Note: For the detail oriented among us, I know that you can't JSON.generate() a number or a string. In these cases, I would just call MD5.hexdigest() directly.
I coding up the following pretty quickly and don't have time to really test it here at work, but it ought to do the job. Let me know if you find any issues with it and I'll take a look.
This should properly flatten out and sort the arrays and hashes, and you'd need to have to some pretty strange looking strings for there to be any collisions.
def createsig(body)
Digest::MD5.hexdigest( sigflat body )
end
def sigflat(body)
if body.class == Hash
arr = []
body.each do |key, value|
arr << "#{sigflat key}=>#{sigflat value}"
end
body = arr
end
if body.class == Array
str = ''
body.map! do |value|
sigflat value
end.sort!.each do |value|
str << value
end
end
if body.class != String
body = body.to_s << body.class.to_s
end
body
end
> sigflat({:a => {:b => 'b', :c => 'c'}, :d => 'd'}) == sigflat({:d => 'd', :a => {:c => 'c', :b => 'b'}})
=> true
If you could only get a string representation of body and not have the Ruby 1.8 hash come back with different orders from one time to the other, you could reliably hash that string representation. Let's get our hands dirty with some monkey patches:
require 'digest/md5'
class Object
def md5key
to_s
end
end
class Array
def md5key
map(&:md5key).join
end
end
class Hash
def md5key
sort.map(&:md5key).join
end
end
Now any object (of the types mentioned in the question) respond to md5key by returning a reliable key to use for creating a checksum, so:
def createsig(o)
Digest::MD5.hexdigest(o.md5key)
end
Example:
body = [
{
'bar' => [
345,
"baz",
],
'qux' => 7,
},
"foo",
123,
]
p body.md5key # => "bar345bazqux7foo123"
p createsig(body) # => "3a92036374de88118faf19483fe2572e"
Note: This hash representation does not encode the structure, only the concatenation of the values. Therefore ["a", "b", "c"] will hash the same as ["abc"].
Here's my solution. I walk the data structure and build up a list of pieces that get joined into a single string. In order to ensure that the class types seen affect the hash, I inject a single unicode character that encodes basic type information along the way. (For example, we want ["1", "2", "3"].objsum != [1,2,3].objsum)
I did this as a refinement on Object, it's easily ported to a monkey patch. To use it just require the file and run "using ObjSum".
module ObjSum
refine Object do
def objsum
parts = []
queue = [self]
while queue.size > 0
item = queue.shift
if item.kind_of?(Hash)
parts << "\\000"
item.keys.sort.each do |k|
queue << k
queue << item[k]
end
elsif item.kind_of?(Set)
parts << "\\001"
item.to_a.sort.each { |i| queue << i }
elsif item.kind_of?(Enumerable)
parts << "\\002"
item.each { |i| queue << i }
elsif item.kind_of?(Fixnum)
parts << "\\003"
parts << item.to_s
elsif item.kind_of?(Float)
parts << "\\004"
parts << item.to_s
else
parts << item.to_s
end
end
Digest::MD5.hexdigest(parts.join)
end
end
end
Just my 2 cents:
module Ext
module Hash
module InstanceMethods
# Return a string suitable for generating content signature.
# Signature image does not depend on order of keys.
#
# {:a => 1, :b => 2}.signature_image == {:b => 2, :a => 1}.signature_image # => true
# {{:a => 1, :b => 2} => 3}.signature_image == {{:b => 2, :a => 1} => 3}.signature_image # => true
# etc.
#
# NOTE: Signature images of identical content generated under different versions of Ruby are NOT GUARANTEED to be identical.
def signature_image
# Store normalized key-value pairs here.
ar = []
each do |k, v|
ar << [
k.is_a?(::Hash) ? k.signature_image : [k.class.to_s, k.inspect].join(":"),
v.is_a?(::Hash) ? v.signature_image : [v.class.to_s, v.inspect].join(":"),
]
end
ar.sort.inspect
end
end
end
end
class Hash #:nodoc:
include Ext::Hash::InstanceMethods
end
These days there is a formally defined method for canonicalizing JSON, for exactly this reason: https://datatracker.ietf.org/doc/html/draft-rundgren-json-canonicalization-scheme-16
There is a ruby implementation here: https://github.com/dryruby/json-canonicalization
Depending on your needs, you could call ary.inspect or ary.to_yaml, even.
I'm creating a nested hash in ruby rexml and want to update the hash when i enter a loop.
My code is like:
hash = {}
doc.elements.each(//address) do |n|
a = # ...
b = # ...
hash = { "NAME" => { a => { "ADDRESS" => b } } }
end
When I execute the above code the hash gets overwritten and I get only the info in the last iteration of the loop.
I don't want to use the following way as it makes my code verbose
hash["NAME"] = {}
hash["NAME"][a] = {}
and so on...
So could someone help me out on how to make this work...
Assuming the names are unique:
hash.merge!({"NAME" => { a => { "ADDRESS" => b } } })
You always create a new hash in each iteration, which gets saved in hash.
Just assign the key directly in the existing hash:
hash["NAME"] = { a => { "ADDRESS" => b } }
hash = {"NAME" => {}}
doc.elements.each('//address') do |n|
a = ...
b = ...
hash['NAME'][a] = {'ADDRESS' => b, 'PLACE' => ...}
end
blk = proc { |hash, key| hash[key] = Hash.new(&blk) }
hash = Hash.new(&blk)
doc.elements.each('//address').each do |n|
a = # ...
b = # ...
hash["NAME"][a]["ADDRESS"] = b
end
Basically creates a lazily instantiated infinitely recurring hash of hashes.
EDIT: Just thought of something that could work, this is only tested with a couple of very simple hashes so may have some problems.
class Hash
def can_recursively_merge? other
Hash === other
end
def recursive_merge! other
other.each do |key, value|
if self.include? key and self[key].can_recursively_merge? value
self[key].recursive_merge! value
else
self[key] = value
end
end
self
end
end
Then use hash.recursive_merge! { "NAME" => { a => { "ADDRESS" => b } } } in your code block.
This simply recursively merges a heirachy of hashes, and any other types if you define the recursive_merge! and can_recusively_merge? methods on them.
Consider this extension to Enumerable:
module Enumerable
def hash_on
h = {}
each do |e|
h[yield(e)] = e
end
h
end
end
It is used like so:
people = [
{:name=>'fred', :age=>32},
{:name=>'barney', :age=>42},
]
people_hash = people.hash_on { |person| person[:name] }
p people_hash['fred'] # => {:age=>32, :name=>"fred"}
p people_hash['barney'] # => {:age=>42, :name=>"barney"}
Is there a built-in function which already does this, or close enough to it that this extension is not needed?
Enumerable.to_h converts a sequence of [key, value]s into a Hash so you can do:
people.map {|p| [p[:name], p]}.to_h
If you have multiple values mapped to the same key this keeps the last one.
[ {:name=>'fred', :age=>32},
{:name=>'barney', :age=>42},
].group_by { |person| person[:name] }
=> {"fred"=>[{:name=>"fred", :age=>32}],
"barney"=>[{:name=>"barney", :age=>42}]}
Keys are in form of arrays to have a possibility to have a several Freds or Barneys, but you can use .map to reconstruct if you really need.