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

StackOverflow https://stackoverflow.com/questions/23459223

  •  15-07-2023
  •  | 
  •  

Question

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.

Was it helpful?

Solution 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

OTHER TIPS

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 }).

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top