-1

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.

Ry-
  • 218,210
  • 55
  • 464
  • 476
amaridev
  • 31
  • 4
  • You meant write 2 instead of 12 in your first hash? – Rajagopalan Jan 18 '19 at 01:46
  • “map obviously returns my values as array”… but you want an array. So you can use `map` again, if you know how to write a function that converts `["Canada", 2]` into `{"country": "Canada", "count": 2}`. – Ry- Jan 18 '19 at 01:48
  • 1
    What does the input form look like? – tadman Jan 18 '19 at 01:57
  • 1
    When you give examples they should be complete (e.g. no "and so on"), so that readers can test your example against their code. You did give the desired result in your example (good), but not the given array (presumably, `countries= [['Canada', 2], ['Chile', 1], ['China', 1]]`). While that may be obvious, it clarifies the example and allows readers to cut-and-paste. Note also that I assigned a variable `countries` to that array. By doing so, readers can refer to that variable in answers and comments without having to define it. A variable should be assigned to each input n examples. – Cary Swoveland Jan 18 '19 at 03:42

2 Answers2

4

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}]
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

Just out of curiosity:

countries = [['Canada', 2], ['Chile', 1], ['China', 1]]
countries.map(&%i[country count].method(:zip)).map(&:to_h)
#⇒ [{:country=>"Canada", :count=>2},
#   {:country=>"Chile", :count=>1},
#   {:country=>"China", :count=>1}]
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160