Search code examples
rubyruby-hash

Translate Ruby hash (key,value) to separate keys


I have a map function in ruby which returns an array of arrays with two values in each, which I want to have in a different format.

What I want to have:

"countries": [
  {
     "country": "Canada",
     "count": 12
   },
   {and so on... }
]

But map obviously returns my values as array:

"countries": [
    [
        "Canada",
        2
    ],
    [
        "Chile",
        1
    ],
    [
        "China",
        1
    ]
]

When using Array::to_h I am also able to bringt it closer to the format I actually want to have.

"countries": {
    "Canada": 2,
    "Chile": 1,
    "China": 1,
}

I have tried reduce/inject, each_with_object but in both cases I do not understand how to access the incoming parameters. While searching here you find many many similar problems. But haven't found a way to adapt those to my case. Hope you can help to find a short and elegant solution.


Solution

  • You are given two arrays:

    countries= [['Canada', 2], ['Chile', 1], ['China', 1]]
    keys = [:country, :count]
    

    You could write

    [keys].product(countries).map { |arr| arr.transpose.to_h }
      #=> [{:country=>"Canada", :count=>2},
      #    {:country=>"Chile", :count=>1},
      #    {:country=>"China", :count=>1}]
    

    or simply

    countries.map { |country, cnt| { country: country, count: cnt } }
      #=> [{:country=>"Canada", :count=>2},
      #    {:country=>"Chile", :count=>1},
      #    {:country=>"China", :count=>1}]
    

    but the first has the advantage that no code need be changed in the names of the keys change. In fact, there would be no need to change the code if the arrays countries and keys both changed, provided countries[i].size == keys.size for all i = 0..countries.size-1. (See the example at the end.)

    The initial step for the first calculation is as follows.

    a = [keys].product(countries)
      #=> [[[:country, :count], ["Canada", 2]],
      #    [[:country, :count], ["Chile", 1]],
      #    [[:country, :count], ["China", 1]]] 
    

    See Array#product. We now have

    a.map { |arr| arr.transpose.to_h }
    

    map passes the first element of a to the block and sets the block variable arr to that value:

    arr = a.first
      #=> [[:country, :count], ["Canada", 2]]
    

    The block calculation is then performed:

    b = arr.transpose
      #=> [[:country, "Canada"], [:count, 2]] 
    b.to_h
      #=> {:country=>"Canada", :count=>2}
    

    So we see that a[0] (arr) is mapped to {:country=>"Canada", :count=>2}. The next two elements of a are then passed to the block and similar calculations are made, after which map returns the desired array of three hashes. See Array#transpose and Array#to_h.

    Here is a second example using the same code.

    countries= [['Canada', 2, 9.09], ['Chile', 1, 0.74],
                ['China', 1, 9.33], ['France', 1, 0.55]]
    
    keys = [:country, :count, :area]
    
    [keys].product(countries).map { |arr| arr.transpose.to_h }
      #=> [{:country=>"Canada", :count=>2, :area=>9.09},
      #    {:country=>"Chile", :count=>1, :area=>0.74},
      #    {:country=>"China", :count=>1, :area=>9.33},
      #    {:country=>"France", :count=>1, :area=>0.55}]