How to sort a hash within a hash? - ruby

I'm trying to sort a hash within a hash. I'd like to sort the hash by the sub-key. I'm using ruby.
I've tried using the sort_by method and iterating the over the hash to reorganize the sub-key. I received the error "ArgumentError: comparison of Hash with Hash failed"
hash = {2012 => {"regularseason" => [game_1, game_2, game_3],
"post_season" => [game_4, game_5]
},
2013 => {"regularseason" => [game_6, game_7, game_8],
"post_season" => [game_9, game_10]
},
2014 => {"post_season" => [game_11, game_12, game_13],
"regularseason" => [game_14, game_15]
}
}
Desired Result:
I would like to sort this hash so sub-key post_season will always appear before sub-key regularseason.

Use Hash#transform_values to sort values:
hash.transform_values { |v| v.sort.to_h }
#⇒ {2012=>{"post_season"=>[:game_4, :game_5],
# "regularseason"=>[:game_1, :game_2, :game_3]},
# 2013=>{"post_season"=>[:game_9, :game_10],
# "regularseason"=>[:game_6, :game_7, :game_8]},
# 2014=>{"post_season"=>[:game_11, :game_12, :game_13],
# "regularseason"=>[:game_14, :game_15]}}

Hashes return keys in the order they are inserted so I believe you'll essentially need to rewrite the nested hash.
For example:
hash.each { |(key, nested_hash)| hash[key] = nested_hash.sort.to_h }
This is rather inefficient, however, so you'd be better to see if you can ensure they are always entered in that order or somehow extract them in the order you desire.

Given a hash with keys that include k, we can return a new hash with the same key/value pairs, with k being the first key inserted, and the remaining keys maintaining their original relative order, as follows:
def reorder_key(h, key)
{ key=>h[key] }.merge h
end
For example:
h = { 1=>2, :a=>:b, "c"=>"d" }
reorder_key(h, :a)
#=> {:a=>:b, 1=>2, "c"=>"d"}
We can use this method to obtain the desired hash in the present problem.
hash.transform_values { |h| reorder_key(h, "post_season") }
#=> {2012=>{"post_season" =>[:game_4, :game_5],
# "regularseason"=>[:game_1, :game_2, :game_3]},
# 2013=>{"post_season" =>[:game_9, :game_10],
# "regularseason"=>[:game_6, :game_7, :game_8]},
# 2014=>{"post_season" =>[:game_11, :game_12, :game_13],
# "regularseason"=>[:game_14, :game_15]}}
This approach does not depend on "post_season" coincidentally preceding "regularseason" lexicographically. If, for example, it were decided to add the key "spring_training" and make that the first key to appear for each year in the returned hash, it would only be necessary to change the value of the second argument of reorder_key to "spring_training".

Related

Ruby : Extract Key and Value from hash

I have an array of hashes that I'm having trouble with extracting the key and value. The array looks like:
data = [{"key"=>"Name", "value"=>"Jason"}, {"key"=>"Age", "value"=>"21"},
{"key"=>"last_name", "value"=>"bourne"}]
How can I convert this to the following array of hashes?
[{"Name"=>"Jason"}, {"Age"=>"21"}, {"last_name"=>"bourne"}]
I was able to use detect :
a = d.detect { |x| x["key"] == "Name" }
puts a['value']
to get the value for "name", but would like to know if there is a better way.
I'd say that the most elegant way to go about this is probably to convert data into a Hash first (assuming there are never any duplicate keys), like so:
data = data.map { |x| [x['key'], x['value']] }.to_h
# => {"Name"=>"Jason", "Age"=>"21", "last_name"=>"bourne"}
The #to_h method expects each element of the array to be an array in the form [key, value], so the #map call processes each element of data to convert it into that form.
Once this has been done, you can simply access keys like any other hash:
data['Name'] # => "Jason"
data['Age'] # => "21"
The calculation should not depend on the keys of the hashes, in case they are changed.
data.map { |h| [h.values].to_h }
#=> [{"Name"=>"Jason"}, {"Age"=>"21"}, {"last_name"=>"bourne"}]

compute hash value of same key ruby

I start with a basic hash where the key is a string and value an integer.
hash = {"a"=>2, "b"=>3}
Then what I try to achieve is that i want to push several times into that hash a new hash with different keys or / and same :
hash2 = {"c"=>4, "a"=>5}
The result should be
h_result = {"a"=>7, "b"=>3, "c"=>4}
The first thing would be to push the new hash and keep the duplicate keys.
I saw that answer = How can I merge two hashes without overwritten duplicate keys in Ruby? but it seems that it's not working..
Then I think I should match the same keys and compute the values. But again I can't find the answer.
Thanks guys
If you just want to compute equal keys in the hash what you are looking for is the merge method in the Hash class.
https://ruby-doc.org/core-2.2.1/Hash.html#method-i-merge
Returns a new hash containing the contents of other_hash and the
contents of hsh. If no block is specified, the value for entries with
duplicate keys will be that of other_hash. Otherwise the value for
each duplicate key is determined by calling the block with the key,
its value in hsh and its value in other_hash.
When you pass a block to the merge method it will yield both old value and new value, and the you can do your computation there.
For instance:
hash = {"a"=>2, "b"=>3}
hash2 = {"c"=>4, "a"=>5}
result = hash.merge(hash2) { |key, old_val, new_val| old_val + new_val }
p result #=> {"a"=>7, "b"=>3, "c"=>4}
Just use Hash#merge with a block and tell Ruby what to do when the key exists in both hashes – in this example just add the value from the second hash to the value from the first hash.
hash.merge(hash2) { |key, v1, v2| v1 + v2 }
#=> { "a" => 7, "b" => 3, "c" => 4 }

Ruby how to return an element of a dictionary?

# dictionary = {"cat"=>"Sam"}
This a return a key
#dictionary.key(x)
This returns a value
#dictionary[x]
How do I return the entire element
"cat"=>"Sam"
#dictionary
should do the trick for you
whatever is the last evaluated expression in ruby is the return value of a method.
If you want to return the hash as a whole. the last line of the method should look like the line I have written above
Your example is a bit (?) misleading in a sense it only has one pair (while not necessarily), and you want to get one pair. What you call a "dictionary" is actually a hashmap (called a hash among Rubyists).
A hashrocket (=>) is a part of hash definition syntax. It can't be used outside it. That is, you can't get just one pair without constructing a new hash. So, a new such pair would look as: { key => value }.
So in order to do that, you'll need a key and a value in context of your code somewhere. And you've specified ways to get both if you have one. If you only have a value, then:
{ #dictionary.key(x) => x }
...and if just a key, then:
{ x => #dictionary[x] }
...but there is no practical need for this. If you want to process each pair in a hash, use an iterator to feed each pair into some code as an argument list:
#dictionary.each do |key, value|
# do stuff with key and value
end
This way a block of code will get each pair in a hash once.
If you want to get not a hash, but pairs of elements it's constructed of, you can convert your hash to an array:
#dictionary.to_a
# => [["cat", "Sam"]]
# Note the double braces! And see below.
# Let's say we have this:
#dictionary2 = { 1 => 2, 3 => 4}
#dictionary2[1]
# => 2
#dictionary2.to_a
# => [[1, 2], [3, 4]]
# Now double braces make sense, huh?
It returns an array of pairs (which are arrays as well) of all elements (keys and values) that your hashmap contains.
If you wish to return one element of a hash h, you will need to specify the key to identify the element. As the value for key k is h[k], the key-value pair, expressed as an array, is [k, h[k]]. If you wish to make that a hash with a single element, use Hash[[[k, h[k]]]].
For example, if
h = { "cat"=>"Sam", "dog"=>"Diva" }
and you only wanted to the element with key "cat", that would be
["cat", h["cat"]] #=> ["cat", "Sam"]
or
Hash[[["cat", h["cat"]]]] #=> {"cat"=>"Sam"}
With Ruby 2.1 you could alternatively get the hash like this:
[["cat", h["cat"]]].to_h #=> {"cat"=>"Sam"}
Let's look at a little more interesting case. Suppose you have an array arr containing some or all of the keys of a hash h. Then you can get all the key-value pairs for those keys by using the methods Enumerable#zip and Hash#values_at:
arr.zip(arr.values_at(*arr))
Suppose, for example,
h = { "cat"=>"Sam", "dog"=>"Diva", "pig"=>"Petunia", "owl"=>"Einstein" }
and
arr = ["dog", "owl"]
Then:
arr.zip(h.values_at(*arr))
#=> [["dog", "Diva"], ["owl", "Einstein"]]
In steps:
a = h.values_at(*arr)
#=> h.values_at(*["dog", "owl"])
#=> h.values_at("dog", "owl")
#=> ["Diva", "Einstein"]
arr.zip(a)
#=> [["dog", "Diva"], ["owl", "Einstein"]]
To instead express as a hash:
Hash[arr.zip(h.values_at(*arr))]
#=> {"dog"=>"Diva", "owl"=>"Einstein"}
You can get the key and value in one go - resulting in an array:
#h = {"cat"=>"Sam", "dog"=>"Phil"}
key, value = p h.assoc("cat") # => ["cat", "Sam"]
Use rassoc to search by value ( .rassoc("Sam") )

Search one hash for its keys, grab the values, put them in an array and merge the first hash with the second

I have a hash with items like
{'people'=>'50'},
{'chairs'=>'23'},
{'footballs'=>'5'},
{'crayons'=>'1'},
and I have another hash with
{'people'=>'http://www.thing.com/this-post'},
{'footballs'=>'http://www.thing.com/that-post'},
{'people'=>'http://www.thing.com/nice-post'},
{'footballs'=>'http://www.thing.com/other-post'},
{'people'=>'http://www.thing.com/thingy-post'},
{'footballs'=>'http://www.thing.com/the-post'},
{'people'=>'http://www.thing.com/the-post'},
{'crayons'=>'http://www.thing.com/the-blah'},
{'chairs'=>'http://www.thing.com/the-page'},
and I want something like the following that takes the first hash and then looks through the second one, grabs all the links for each word and puts them into a array appended onto the end of the hash somehow.
{'people', '50' => {'http://www.thing.com/this-post', 'http://www.thing.com/nice-post', 'http://www.thing.com/thingy-post'}},
{'footballs', '5' => {'http://www.thing.com/the-post', 'http://www.thing.com/the-post'}},
{'crayons', '1' => {'http://www.thing.com/the-blah'}},
{'chairs', '23' => {'chairs'=>'http://www.thing.com/the-page'}},
I am very new to Ruby, and I have tried quite a few combinations, and I need some help.
Excuse the example, I hope that it makes sense.
What you have is a mix of hashes, arrays, and something in the middle. I'm going to assume the following inputs:
categories = {'people'=>'50',
'chairs'=>'23',
'footballs'=>'5',
'crayons'=>'1'}
and:
posts = [['people', 'http://www.thing.com/this-post'],
['footballs','http://www.thing.com/that-post'],
['people','http://www.thing.com/nice-post'],
['footballs','http://www.thing.com/other-post'],
['people','http://www.thing.com/thingy-post'],
['footballs','http://www.thing.com/the-post'],
['people','http://www.thing.com/the-post'],
['crayons','http://www.thing.com/the-blah'],
['chairs','http://www.thing.com/the-page']]
and the following output:
[['people', '50', ['http://www.thing.com/this-post', 'http://www.thing.com/nice-post', 'http://www.thing.com/thingy-post']],
[['footballs', '5', ['http://www.thing.com/the-post', 'http://www.thing.com/the-post']],
['crayons', '1', ['http://www.thing.com/the-blah']],
['chairs', '23' => {'chairs'=>'http://www.thing.com/the-page']]]
In which case what you would need is:
categories.map do |name, count|
[name, count, posts.select do |category, _|
category == name
end.map { |_, post| post }]
end
You need to understand the different syntax for Array and Hash in Ruby:
Hash:
{ 'key1' => 'value1',
'key2' => 'value2' }
Array:
[ 'item1', 'item2', 'item3', 'item4' ]
A Hash in ruby (like in every other language) can't have more than once instance of any single key, meaning that a Hash {'key1' => 1, 'key1' => 2} is invalid and will result in an unexpected value (duplicate keys are overridden - you'll have {'key1' => 2 }).
Since there is some confusion about the format of the data, I will suggest how you might effectively structure both the input and the output. I will first present some code you could use, then give an example of how it's used, then explain what is happening.
Code
def merge_em(hash, array)
hash_keys = hash.keys
new_hash = hash_keys.each_with_object({}) { |k,h|
h[k] = { qty: hash[k], http: [] } }
array.each do |h|
h.keys.each do |k|
(new_hash.update({k=>h[k]}) { |k,g,http|
{qty: g[:qty], http: (g[:http] << http)}}) if hash_keys.include?(k)
end
end
new_hash
end
Example
Here is a hash that I have modified to include a key/value pair that does not appear in the array below:
hash = {'people' =>'50', 'chairs' =>'23', 'footballs'=>'5',
'crayons'=> '1', 'cat_lives'=> '9'}
Below is your array of hashes that is to be merged into hash. You'll see I've added a key/value pair to your hash with key "chairs". As I hope to make clear, the code is no different (i.e., not simplified) if we know in advance that each hash has only one key value pair. (Aside: if, for example, we want want the key from a hash h that is known to have only one key, we still have to pull out all the keys into an array and then take the only element of the array: h.keys.first).
I have also added a hash to the array that has no key that is among hash's keys.
array =
[{'people' =>'http://www.thing.com/this-post'},
{'footballs'=>'http://www.thing.com/that-post'},
{'people' =>'http://www.thing.com/nice-post'},
{'footballs'=>'http://www.thing.com/other-post'},
{'people' =>'http://www.thing.com/thingy-post'},
{'footballs'=>'http://www.thing.com/the-post'},
{'people' =>'http://www.thing.com/the-post'},
{'crayons' =>'http://www.thing.com/the-blah'},
{'chairs' =>'http://www.thing.com/the-page',
'crayons' =>'http://www.thing.com/blah'},
{'balloons' =>'http://www.thing.com/the-page'}
]
We now merge the information from array into hash, and at the same time change the structure of hash to something more suitable:
result = merge_em(hash, array)
#=> {"people" =>{:qty=>"50",
# :http=>["http://www.thing.com/this-post",
# "http://www.thing.com/nice-post",
# "http://www.thing.com/thingy-post",
# "http://www.thing.com/the-post"]},
# "chairs" =>{:qty=>"23",
# :http=>["http://www.thing.com/the-page"]},
# "footballs"=>{:qty=>"5",
# :http=>["http://www.thing.com/that-post",
# "http://www.thing.com/other-post",
# "http://www.thing.com/the-post"]},
# "crayons" =>{:qty=>"1",
# :http=>["http://www.thing.com/the-blah",
# "http://www.thing.com/blah"]},
# "cat_lives"=>{:qty=>"9",
# :http=>[]}}
I've assumed you want to look up the content of result with hash's keys. It is therefore convenient to make the values associated with those keys hashes themselves, with keys :qty and http. The former is for the values in hash (the naming may be wrong); the latter is an array containing the strings drawn from array.
This way, if we want the value for the key "crayons", we could write:
result["crayons"]
#=> {:qty=>"1",
# :http=>["http://www.thing.com/the-blah", "http://www.thing.com/blah"]}
or
irb(main):133:0> result["crayons"][:qty]
#=> "1"
irb(main):134:0> result["crayons"][:http]
#=> ["http://www.thing.com/the-blah", "http://www.thing.com/blah"]
Explanation
Let's go through this line-by-line. First, we need to reference hash.keys more than once, so let's make it a variable:
hash_keys = hash.keys
#=> ["people", "chairs", "footballs", "crayons", "cat_lives"]
We may as well convert this hash to the output format now. We could do it during the merge operation below, but I think is clearer to do it as a separate step:
new_hash = hash_keys.each_with_object({}) { |k,h|
h[k] = { qty: hash[k], http: [] } }
#=> {"people" =>{:qty=>"50", :http=>[]},
# "chairs" =>{:qty=>"23", :http=>[]},
# "footballs"=>{:qty=>"5", :http=>[]},
# "crayons" =>{:qty=>"1", :http=>[]},
# "cat_lives"=>{:qty=>"9", :http=>[]}}
Now we merge each (hash) element of array into new_hash:
array.each do |h|
h.keys.each do |k|
(new_hash.update({k=>h[k]}) { |k,g,http|
{ qty: g[:qty], http: (g[:http] << http) } }) if hash_keys.include?(k)
end
end
The first hash h from array that is passed into the block by each is:
{'people'=>'http://www.thing.com/this-post'}
which is assigned to the block variable h. We next construct an array of h's keys:
h.keys #=> ["people"]
The first of these keys, "people" (pretend there were more, as there would be in the penultimate element of array) is passed by its each into the inner block, whose block variable, k, is assigned the value "people". We then use Hash#update (aka merge!) to merge the hash:
{k=>h[k]} #=> {"people"=>'http://www.thing.com/this-post'}
into new_hash, but only because:
hash_keys.include?(k)
#=> ["people", "chairs", "footballs", "crayons", "cat_lives"].include?("people")
#=> true
evaluates to true. Note that this will evaluate to false for the key "balloons", so the hash in array with that key will not be merged. update's block:
{ |k,g,http| { qty: g[:qty], http: (g[:http] << http) } }
is crucial. This is update's way of determining the value of a key that is in both new_hash and in the hash being merged, {k=>h[k]}. The three block variables are assigned the following values by update:
k : the key ("people")
g : the current value of `new_hash[k]`
#=> `new_hash["people"] => {:qty=>"50", :http=>[]}`
http: the value of the key/value being merged
#=> 'http://www.thing.com/this-post'
We want the merged hash value for key "people" to be:
{ qty: g[:qty], http: (g[:http] << http) }
#=> { qty: 50, http: ([] << 'http://www.thing.com/this-post') }
#=> { qty: 50, http: ['http://www.thing.com/this-post'] }
so now:
new_hash
#=> {"people" =>{:qty=>"50", :http=>['http://www.thing.com/this-post']},
# "chairs" =>{:qty=>"23", :http=>[]},
# "footballs"=>{:qty=>"5", :http=>[]},
# "crayons" =>{:qty=>"1", :http=>[]},
# "cat_lives"=>{:qty=>"9", :http=>[]}}
We do the same for each of the other elements of array.
Lastly, we need to return the merged new_hash, so we make last line of the method:
new_hash
You could also do this
cat = [{'people'=>'50'},
{'chairs'=>'23'},
{'footballs'=>'5'},
{'crayons'=>'1'}]
pages = [{'people'=>'http://www.thing.com/this-post'},
{'footballs'=>'http://www.thing.com/that-post'},
{'people'=>'http://www.thing.com/nice-post'},
{'footballs'=>'http://www.thing.com/other-post'},
{'people'=>'http://www.thing.com/thingy-post'},
{'footballs'=>'http://www.thing.com/the-post'},
{'people'=>'http://www.thing.com/the-post'},
{'crayons'=>'http://www.thing.com/the-blah'},
{'chairs'=>'http://www.thing.com/the-page'}]
cat.map do |c|
c.merge(Hash['pages',pages.collect{|h| h[c.keys.pop]}.compact])
end
#=> [{"people"=>"50", "pages"=>["http://www.thing.com/this-post", "http://www.thing.com/nice-post", "http://www.thing.com/thingy-post", "http://www.thing.com/the-post"]},
{"chairs"=>"23", "pages"=>["http://www.thing.com/the-page"]},
{"footballs"=>"5", "pages"=>["http://www.thing.com/that-post", "http://www.thing.com/other-post", "http://www.thing.com/the-post"]},
{"crayons"=>"1", "pages"=>["http://www.thing.com/the-blah"]}]
Which is closer to your request but far less usable than some of the other posts.

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