Let's say I have a Hash like this:
my_hash = {"a"=>{"a1"=>"b1"}, "b"=>"b", "c"=>{"c1"=>{"c2"=>"c3"}}}
And I want to convert every element inside the hash that is also a hash to be placed inside of an Array.
For example, I want the finished Hash to look like this:
{"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>[{"c2"=>"c3"}]}]}
Here is what I've tried so far, but I need it to work recursively and I'm not quite sure how to make that work:
my_hash.each do |k,v|
if v.class == Hash
my_hash[k] = [] << v
end
end
=> {"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>{"c2"=>"c3"}}]}
You need to wrap your code into a method and call it recursively.
my_hash = {"a"=>{"a1"=>"b1"}, "b"=>"b", "c"=>{"c1"=>{"c2"=>"c3"}}}
def process(hash)
hash.each do |k,v|
if v.class == Hash
hash[k] = [] << process(v)
end
end
end
p process(my_hash)
#=> {"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>[{"c2"=>"c3"}]}]}
Recurring proc is another way around:
h = {"a"=>{"a1"=>"b1"}, "b"=>"b", "c"=>{"c1"=>{"c2"=>"c3"}}}
h.map(&(p = proc{|k,v| {k => v.is_a?(Hash) ? [p[*v]] : v}}))
.reduce({}, &:merge)
# => {"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>[{"c2"=>"c3"}]}]}
It can be done with single reduce, but that way things get even more obfuscated.
Related
I'm trying to write a method that removes all keys in a nested hash that point to nil recursively.
For example:
{:a=>nil, :b=>"b", :c=>nil, :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}
becomes:
{:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}
I'm not having much luck though.
My most recent attempt looks like:
def deep_compact(hash)
hash.reject do |key, value|
deep_compact(value) if value.class == Hash
next true if value.nil? || value.empty?
end
end
Here I want to iterate over each key value pair in the hash. If the value is a hash, I want to do the same for that hash. I want to reject the pair if the value is nil or empty. Otherwise, I want to keep it.
The result isn't what I want:
#=> {:b=>"b", :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}
I have also tried:
def deep_compact(hash)
hash.compact.transform_values do |value|
deep_compact(value) if value.class == Hash
value
end
end
Again, I get the same result:
#=> {:b=>"b", :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}
This leaves me to believe that either I've missed something or my understanding of recursion is wrong.
Are any of my attempts close? What do I need to do to ensure I get the result I want: {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}?
The trick would be to recursively compact nested hashes and then to eliminate empty values.
compact = ->(hash) {
hash.is_a?(Hash) ?
hash.map { |k, v| [k, compact.(v)] }.
to_h.
delete_if { |_, v| v.nil? || v.respond_to?(:empty?) && v.empty? } :
hash
}
compact.(input)
#⇒ {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}
I discovered that by placing my recursive function call at the end of the block got me most of the way there. (Is this 'tail-end' recursion?)
I also call reject on the hash returned by transform_values to removed any empty pairs.
This achieves what I wanted:
def deep_compact(hash)
hash.compact.transform_values do |value|
next value unless value.class == Hash
deep_compact(value)
end.reject { |_k, v| v.empty? }
end
> h
=> {:a=>nil, :b=>"b", :c=>nil, :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}
> deep_compact h
=> {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}
Other option using Hash#reject!, it changes the original Hash:
def deep_compact(h)
h.each { |_, v| deep_compact(v) if v.is_a? Hash }.reject! { |_, v| v.nil? || v.empty? }
end
deep_compact(h)
#=> {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}
Is there a more elegant way to write this code?
def create_a_hash_from_a_collection
my_hash = {}
collection_of_hashes.each do |key, value|
my_hash[key] = {} if my_hash[key].nil?
my_hash[key] = value
end
my_hash
end
The line that seems clumsy to me is this:
my_hash[key] = {} if my_hash[key].nil?
Is there a shorthand way of expressing it?
If you want to have a brand new hash for each key in your initial hash you need to initialise it with a block:
hash = Hash.new { |hash, key| hash[key] = {} }
hash[:foo].object_id == hash[:bar].object_id #=> false
Otherwise, if you do this, It will be always the same default hash
hash = Hash.new({})
hash[:foo].object_id == hash[:bar].object_id #=> true
You can use ||= operator which does exactly what you want
my_hash[key] ||= {}
The rest of my answer here is because I'm not sure what a "collection of hashes" is, so my best guess is that it would be an array of hashes. If I'm wrong, let me know and disregard the rest of this answer.
It seems that the rest of your method may not do what it sounds like you're trying to do. Consider:
#collection_of_hashes = [{foo: 'bar'}, {baz: 'qux'}]
def create_a_hash_from_a_collection
my_hash = {}
#collection_of_hashes.each do |key, value|
# this is not actually doing anything here and returns same with or
# without the following line
# my_hash[key] ||= {}
my_hash[key] = value
end
my_hash
end
#=> {{:foo=>"bar"}=>nil, {:baz=>"qux"}=>nil}
But what you probably want is
def create_a_hash_from_a_collection
my_hash = {}
#collection_of_hashes.each do |hash|
hash.keys.each do |k|
my_hash[k] = hash[k]
end
end
my_hash
end
#=> {:foo=>"bar", :baz=>"qux"}
But also keep in mind, if any of your "collection of hashes" which we would tend to assume would be an array of hashes, contain the same key, which one wins? This code, it would be the last item in the array's key value. What is the actual goal of your method?
I guess what you want is to initialise your my_hash with a default value, so then you don't need to check if it's nil or not.
That can be done using the Hash.new constructor, compare:
my_hash = {}
puts my_hash['no_existing_key'] #=> nil
my_hash = Hash.new({})
puts my_hash['no_existing_key'] #=> {}
You then can reduce your code to:
def create_a_hash_from_a_collection
my_hash = Hash.new({})
collection_of_hashes.each do |key, value|
my_hash[key] = value
end
my_hash
end
Since you are assigning value anyway, maybe you could use my_hash[key] = value || {}?
So, if value to assign is nil, the value of that key becomes {}.
Suppose I have following hash or nested hash:
h = { :a1 => { :b1 => "c1" },
:a2 => { :b2 => "c2"},
:a3 => { :b3 => "c3"} }
I want to create a method that takes hash as a parameter and recursively convert all the keys (keys that are symbol eg. :a1) to String (eg. "a1"). So far I have come up with the following method which doesn't work and returns {"a1"=>{:b1=>"c1"}, "a2"=>{:b2=>"c2"}, "a3"=>{:b3=>"c3"}}.:
def stringify_all_keys(hash)
stringified_hash = {}
hash.each do |k, v|
stringified_hash[k.to_s] = v
if v.class == Hash
stringify_all_keys(stringified_hash[k.to_s])
end
end
stringified_hash
end
What am I doing wrong and how do a get all the keys converted to string like this:
{"a1"=>{"b1"=>"c1"}, "a2"=>{"b2"=>"c2"}, "a3"=>{"b3"=>"c3"}}
If you are using ActiveSupport already or are open to using it, then deep_stringify_keys is what you're looking for.
hash = { person: { name: 'Rob', age: '28' } }
hash.deep_stringify_keys
# => {"person"=>{"name"=>"Rob", "age"=>"28"}}
Quick'n'dirty if your values are basic objects like strings, numbers, etc:
require 'json'
JSON.parse(JSON.dump(hash))
Didn't test this, but looks about right:
def stringify_all_keys(hash)
stringified_hash = {}
hash.each do |k, v|
stringified_hash[k.to_s] = v.is_a?(Hash) ? stringify_all_keys(v) : v
end
stringified_hash
end
using plain ruby code, the below code could help.
you can monkey patched it to the ruby Hash, to use it like this my_hash.deeply_stringfy_keys
however, I do not recommend monkey batching ruby.
you can adjust the method to provide the deeply_strigify_keys! (bang) version of it.
in case you want to make a different method witch does not stringify recursively, or to control the level of stringifying then consider re-writing the below method logic so you can have it written better with considering the other variation mentioned above.
def deeply_stringify_keys(hash)
stringified_hash = {}
hash.each do |k, v|
if v.is_a?(Hash)
stringified_hash[k.to_s] = deeply_stringify_keys(v)
elsif v.is_a?(Array)
stringified_hash[k.to_s] = v.map {|i| i.is_a?(Hash)? deeply_stringify_keys(i) : i}
else
stringified_hash[k.to_s] = v
end
end
stringified_hash
end
I have a hash of key value pairs, similar to -
myhash={'test1' => 'test1', 'test2 => 'test2', ...}
how can I initialize such a hash in a loop? Basically I need it to go from 1..50 with the same test$i values but I cannot figure out how to initialize it properly in a loop instead of doing it manually.
I know how to loop through each key-value pair individually:
myhash.each_pair do |key, value|
but that doesn't help with init
How about:
hash = (1..50).each.with_object({}) do |i, h|
h["test#{i}"] = "test#{i}"
end
If you want to do this lazily, you could do something like below:
hash = Hash.new { |hash, key| key =~ /^test\d+/ ? hash[key] = key : nil}
p hash["test10"]
#=> "test10"
p hash
#=> {"test10"=>"test10"}
The block passed to Hash constructor will be invoked whenever a key is not found in hash, we check whether key follows a certain pattern (based on your need), and create a key-value pair in hash where value is equal to key passed.
(1..50).map { |i| ["test#{i}"] * 2 }.to_h
The solution above is more DRY than two other answers, since "test" is not repeated twice :)
It is BTW, approx 10% faster (that would not be a case when keys and values differ):
require 'benchmark'
n = 500000
Benchmark.bm do |x|
x.report { n.times do ; (1..50).map { |i| ["test#{i}"] * 2 }.to_h ; end }
x.report { n.times do ; (1..50).each.with_object({}) do |i, h| ; h["test#{i}"] = "test#{i}" ; end ; end }
end
user system total real
17.630000 0.000000 17.630000 ( 17.631221)
19.380000 0.000000 19.380000 ( 19.372783)
Or one might use eval:
hash = {}
(1..50).map { |i| eval "hash['test#{i}'] = 'test#{i}'" }
or even JSON#parse:
require 'json'
JSON.parse("{" << (1..50).map { |i| %Q|"test#{i}": "test#{i}"| }.join(',') << "}")
First of all, there's Array#to_h, which converts an array of key-value pairs into a hash.
Second, you can just initialize such a hash in a loop, just do something like this:
target = {}
1.upto(50) do |i|
target["test_#{i}"] = "test_#{i}"
end
You can also do this:
hash = Hash.new{|h, k| h[k] = k.itself}
(1..50).each{|i| hash["test#{i}"]}
hash # => ...
I’m having a few problems creating a hash out of 2 arrays in ruby (1.9.2)
My issue is some of the hash keys are the same and it seems to cause an issue
So my first array (called listkey) contains these 5 items
puts listkey
service_monitor_errorlog
service_monitor_errorlog
wmt_errorlog
wmt_errorlog
syslog
the second ( called listvalue) contains these 5 items
puts listvalue
service_monitor_errorlog.0
service_monitor_errorlog.current
wmt_errorlog.0
wmt_errorlog.current
syslog.txt
what I want is a hash which contains all 5 items e.g.
{
"service_monitor_errorlog"=>"service_monitor_errorlog.0",
"service_monitor_errorlog"=>"service_monitor_errorlog.current",
"wmt_errorlog"=>"wmt_errorlog.0",
"wmt_errorlog"=>"wmt_errorlog.current",
"syslog"=>"syslog.txt"
}
However using the hash zip command
MyHash = Hash[listkey.zip(listvalue)]
I get this hash produced
puts MyHash
{
"service_monitor_errorlog"=>"service_monitor_errorlog.current",
"wmt_errorlog"=>"wmt_errorlog.current",
"syslog"=>"syslog.txt"
}
Can anyone help? I’ve tried all sorts of commands to merge the 2 arrays into a hash but none of them seem to work
Cheers
Mike
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
EDIT
I've just found out hashs have to have unique keys so could anyone help me work out a way to combine the arrays to form a hash with the values as arrays e.g.
{
"service_monitor_errorlog"=>["service_monitor_errorlog.0", "service_monitor_errorlog.current"]
"wmt_errorlog"=> ["wmt_errorlog.0", "wmt_errorlog.current"]
"syslog"=> ["syslog.txt"]
}
In 1.9 I'd probably do this:
listkey.zip(listvalue).each_with_object(Hash.new{|h,k| h[k] = []}) do |(k,v), h|
h[k] << v
end
Example:
a=['a','b','c','a']
b=[1,2,3,4]
a.zip(b).each_with_object(Hash.new{|h,k| h[k]=[]}) { |(k,v), h| h[k] << v }
#=> {"a"=>[1, 4], "b"=>[2], "c"=>[3]}
For your updated question, an (ugly) solution is
the_hash = listkey.zip(listvalue).inject({}) do | a, (k, v) |
a[k] ||= []
a[k] << v
a
end
or (without the inject)
the_hash = {}
listkey.zip(listvalue).each do | k, v |
the_hash[k] ||= []
the_hash[k] << v
end
Answering the answer after the edit. group_by is a bit inconvenient in this case, so let's use facets' map_by, which is a group_by that allows you to decide what you want to accumulate:
require 'facets'
Hash[xs.zip(ys).map_by { |k, v| [k, v] }]
#=> {"syslog"=>["syslog.txt"],
# "service_monitor_errorlog"=>
# ["service_monitor_errorlog.0", "service_monitor_errorlog.current"],
# "wmt_errorlog"=>["wmt_errorlog.0", "wmt_errorlog.current"]}
Please check this code
a=['a','b','c','a']
b=[1,2,3,4]
c=Hash.new
a.each_with_index do |value,key|
#puts key
#puts value
c[value]=b[key]
end
puts c
Output is
{"a"=>4, "b"=>2, "c"=>3}
This means key should be unique