Array of paths from Nested Hash - ruby

I have a hash
my_hash = {"key1"=> {"key2"=> {"key3"=> "value"}}, "key4"=> "value"}
I want to return only the full path of the keys as an array. All concatenated like this
[key1, key1key2, key1key2key3, key4].
Any suggestions on how to do this. Thanks

I've been able to solve this with the iteraptor helpers suggested by this user. Thanks
I used the feature 'aplanar' i.e
hash.aplanar.keys
returns the paths of all the keys that have values. Just as I wanted.

The following is a recursive method that progressively modifies the keys of inner hashes. For example,
{"key2"=> {"key3"=> "value"}}
is changed to
{"key1key2"=> {"key3"=> "value"}}
after which
{"key3"=> "value"}
is changed to
{"key1key2key3"=> "value"}
This allows me to simply accumulate the keys of these hashes.
def recurse(h)
h.map do |k,v|
next k unless v.is_a?(Hash)
key, val = v.flatten
[k, recurse("#{k}#{key}"=>val)]
end.flatten
end
recurse my_hash
#=> ["key1", "key1key2", "key1key2key3", "key4"]

Related

access hashes of hashes values in ruby

I have a nested hashes in ruby and I need to access a specific value of it. My hash look like below.
hash =
{"list"=>
{"0"=>
{"date"=>"11/03/2014",
"item1"=>"",
"tiem2"=>"News",
"item3"=>"",
"item4"=>"",
"item5"=>"Videos",
"Type"=>"Clip"},
"1"=>
{"date"=>"11/03/2014",
"item1"=>"",
"tiem2"=>"News",
"item3"=>"",
"item4"=>"",
"item5"=>"Videos",
"Type"=>"Program"}
}}
I need to access the value of "Type" of each keys.
I tried with the below code but I am not sure why it didn't work.
hash_type = hash["list"].keys.each {|key| puts key["Type"]}
But it returned the list of keys. i.e 0 and 1
Please help.
hash["list"].map {|_, hash| hash['Type']}
Explanation:
hash = {key: 'value'}
You can loop over a hash using each like this:
hash.each {|pair| puts pair.inspect } #=> [:key, 'value']
or like this
hash.each {|key, value| puts "#{key}: #{value}"} #=> key: value
Since we don't use key anywhere, some of the IDEs will complain about unused local variable key. To prevent this it is ruby convention to use _ for variable name and all the IDEs will not care for it to be unused.
hash['list'].collect { |_, value| value['Type'] }
=> ["Clip", "Program"]
This is following your logic (some answers posted different ways to do this). The reason why you go things wrong, if we go step by step is:
hash_type = hash["list"].keys #=> ["0", "1"]
So everything after that is the same like:
["0", "1"].each {|key| puts key["Type"]}
So you're basically doing puts '1'['Type'] and '0'['Type'] which both evaluate to nil (try it in IRB) . Try replacing the puts with p and you'll get nil printed 2 times. The reason why you're getting hash_type to be ["0", "1"] is because your last expression is keys.each and each ALWAYS return the "receiver", that is the array on which you called each (as we saw earlier, that array is ["0", "1"]).
The key to solving this, following your particular logic, is to put the "keys" (which are '0' and '1' in this instance) in the appropriate context, and putting them in a context would look something like this:
hash_type = hash["list"].keys.each {|key| puts hash["list"][key]["Type"]}`
This will print the keys. However, hash_type will still be ["0", "1"] (remember, each returns the value of the receiver). If you want the actual type values to be stored in hash_types, replace each with map and remove puts:
hash_type = hash["list"].keys.map {|key| hash["list"][key]["Type"]} #=> ["Clip", "Program"]

Converting hash to array of hashes order preserved?

I have a hash h:
h = {145=>1, 137=>2, 34=>3}
I want to convert it into an array of hashes of the form:
[{cid:145, qty:1}, {cid:137, qty:2}, {cid:34, qty:3}]
My first attempt a solution works for this example:
h.keys.zip(h.values).map { |cid, qty| {cid:cid, qty:qty} }
Evaluates to
[{:cid=>145, :qty=>1}, {:cid=>137, :qty=>2}, {:cid=>34, :qty=>3}]
My worry is that h.keys and h.values won't always align, since hashes aren't necessarily ordered.
How can I solve this problem with the guarantee that the keys of h will be paired with their corresponding values?
h = {145=>1, 137=>2, 34=>3}
h.map!{ |k, v| {:cid =>k, :qty => v} }

How to merge array index values and create a hash

I'm trying to convert an array into a hash by using some matching. Before converting the array into a hash, I want to merge the values like this
"Desc,X1XXSC,C,CCCC4524,xxxs,xswd"
and create a hash from it. The rule is that, first value of the array is the key in Hash, in array there are repeating keys, for those keys I need to merge values and place it under one key. "Desc:" are keys. My program looks like this.
p 'test sample application'
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
arr = Array.new
arr = str.split(":")
p arr
test_hash = Hash[*arr]
p test_hash
I could not find a way to figure it out. If any one can guide me, It will be thankful.
Functional approach with Facets:
require 'facets'
str.split(":").each_slice(2).map_by { |k, v| [k, v] }.mash { |k, vs| [k, vs.join] }
#=> {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
Not that you cannot do it without Facets, but it's longer because of some basic abstractions missing in the core:
Hash[str.split(":").each_slice(2).group_by(&:first).map { |k, gs| [k, gs.map(&:last).join] }]
#=> {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
A small variation on #Sergio Tulentsev's solution:
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
str.split(':').each_slice(2).each_with_object(Hash.new{""}){|(k,v),h| h[k] += v}
# => {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
str.split(':') results in an array; there is no need for initializing with arr = Array.new
each_slice(2) feeds the elements of this array two by two to a block or to the method following it, like in this case.
each_with_object takes those two elements (as an array) and passes them on to a block, together with an object, specified by:
(Hash.new{""}) This object is an empty Hash with special behaviour: when a key is not found then it will respond with a value of "" (instead of the usual nil).
{|(k,v),h| h[k] += v} This is the block of code which does all the work. It takes the array with the two elements and deconstructs it into two strings, assigned to k and v; the special hash is assigned to h. h[k] asks the hash for the value of key "Desc". It responds with "", to which "X1" is added. This is repeated until all elements are processed.
I believe you're looking for each_slice and each_with_object here
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
hash = str.split(':').each_slice(2).each_with_object({}) do |(key, value), memo|
memo[key] ||= ''
memo[key] += value
end
hash # => {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}
Enumerable#slice_before is a good way to go.
str = "Desc:X1:C:CCCC:Desc:XXSC:xxxs:xswd:C:4524"
a = ["Desc","C","xxxs"] # collect the keys in a separate collection.
str.split(":").slice_before(""){|i| a.include? i}
# => [["Desc", "X1"], ["C", "CCCC"], ["Desc", "XXSC"], ["xxxs", "xswd"], ["C", "4524"]]
hsh = str.split(":").slice_before(""){|i| a.include? i}.each_with_object(Hash.new("")) do |i,h|
h[i[0]] += i[1]
end
hsh
# => {"Desc"=>"X1XXSC", "C"=>"CCCC4524", "xxxs"=>"xswd"}

Sort items in a nested hash by their values

I'm being sent a nested hash that needs to be sorted by its values. For example:
#foo = {"a"=>{"z"=>5, "y"=>3, "x"=>88}, "b"=>{"a"=>2, "d"=>-5}}
When running the following:
#foo["a"].sort{|a,b| a[1]<=>b[1]}
I get:
[["y", 3], ["z", 5], ["x", 88]]
This is great, it's exactly what I want. The problem is I'm not always going to know what all the keys are that are being sent to me so I need some sort of loop. I tried to do the following:
#foo.each do |e|
e.sort{|a,b| a[1]<=>b[1]}
end
This to me makes sense since if I manually call #foo.first[0] I get
"a"
and #foo.first[1] returns
{"z"=>5, "y"=>3, "x"=>8}
but for some reason this isn't sorting properly (e.g. at all). I assume this is because the each is calling sort on the entire hash object rather than on "a"'s values. How do I access the values of the nested hash without knowing what it's key is?
You might want to loop over the hash like this:
#foo.each do |key, value|
#foo[key] = value.sort{ |a,b| a[1]<=>b[1] }
end
#foo = {"a"=>{"z"=>5, "y"=>3, "x"=>88}, "b"=>{"a"=>2, "d"=>-5}}
#bar = Hash[ #foo.map{ |key,values| [ key, values.sort_by(&:last) ] } ]
Or, via a less-tricky path:
#bar = {}
#foo.each do |key,values|
#bar[key] = values.sort_by{ |key,value| value }
end
In both cases #bar turns out to be:
p #bar
#=> {
#=> "a"=>[["y", 3], ["z", 5], ["x", 88]],
#=> "b"=>[["d", -5], ["a", 2]]
#=> }
My coworker came up with a slightly more flexible solution that will recursively sort an array of any depth:
def deep_sort_by(&block)
Hash[self.map do |key, value|
[if key.respond_to? :deep_sort_by
key.deep_sort_by(&block)
else
key
end,
if value.respond_to? :deep_sort_by
value.deep_sort_by(&block)
else
value
end]
end.sort_by(&block)]
end
You can inject it into all hashes and then just call it like this:
myMap.deep_sort_by { |obj| obj }
The code would be similar for an array. We published it as a gem for others to use, see blog post for additional details.
Disclaimer: I work for this company.
in your example e is an temporary array containing a [key,value] pair. In this case, the character key and the nested hash. So e.sort{|a,b|...} is going to try to compare the character to the hash, and fails with a runtime error. I think you probably meant to type e[1].sort{...}. But even that is not going to work correctly, because you don't store the sorted hash anywhere: #foo.each returns the original #foo and leaves it unchanged.
The better solution is the one suggested by #Pan Thomakos:
#foo.each do |key, value|
#foo[key] = value.sort{ |a,b| a[1]<=>b[1] }
end

Iterate hash for specific range

How to pass range in hash to iterate from index 1 to last?
h = {}
h[1..-1].each_pair do |key,value|
puts "#{key} = #{value}
end
This code returning error. how may i pass range in hash ??
EDIT:
I want to print first key and value without any calculations.
From second key and value i want to do some calculation on my hash.
For that i have written this code ...
store_content_in_hash containing key and values.
first_key_value = store_content_in_hash.shift
f.puts first_key_value[1]
f.puts
store_content_in_hash.each_pair do |key,value|
store_content_in_hash[key].sort.each {|v| f.puts v }
f.puts
end
Any better way to solve out this problem ??
In Ruby 1.9 only:
Given a hash:
h = { :a => :b, :c => :d, :e => :f }
Go Like this:
Hash[Array(h)[1..-1]].each_pair do |key, value|
# ...
end
This will iterate through the following hash { :c => :d, :e => f } as the first key/value pair is excluded by the range.
Hashes have no concept of order. There is no such thing as the first or second element in a hash.
So you can't do what you want with hashes.
Hash is not about the ranges. It's about key value pairs. In ruby 1.8 hash is unordered hence you can't be sure in which order the keys and values will be iterated over which makes "range" thing obsolete. And I believe that you're doing something wrong (tm) in this situation. Can you elaborate on your problem?
On the other note you're getting an error because square brackets in Hash instance accepts keys. So if your hash does not contain 1..-1 as a key - you will get nil value and nil does not respond to each_pair. Try this to get a hold on this:
h = {(1..-1) => {:foo => :bar}}
h[1..-1].each_pair do |key,value|
puts "#{key} = #{value}"
end
As others have pointed out, Hashes are not about order. It's true that 1.9 hashes are ordered, but that's just a convenience, not their primary feature.
If the order is important to you, just use arrays instead of hashes. In your case, an array of pairs (two-element arrays) seems to fit the purpose. And if you need to access it by key, you can always easily convert it to a hash using Hash#[] method.

Resources