user6792790
user6792790

Reputation: 688

Converting an array to hash in ruby

Supposed I have an array that looks like

testarr = [["Actor", "Morgan", "33", ["A","B"]],
  ["Movie", "Titanic", "44", ["A","A"]],
  ["Actor", "Jack Black", "333", ["A","A"]]]

I want to convert this into a hash which will be converted into a json eventually.

I want it to look like

{

    "Actor" => { 
           {   "name" : "Morgan",
               "Age" : 33",
               "Films: { "A", "B" }} ,

           {   "name" : "Jack Black",
               "Age" : 44",
               "Films: { "A", "A" }}
           }
    "Movie" => {
           {    "Title" : "Titanic"
                "Gross" : "44"
                "Actors" : { "A", "A" }
           }
     }

Not sure about the exact format, but whatever makes sense.

I tried

def hashing(arr)
 hash = Hash.new

 arr.each do |item|

     if item[0] == "Movie"
       item.delete("Movie")
       hash["Movie"] = item
       item["Title"] = item[1]
       item["Movie"]["Box Office"] = item[2]
       item["Movie"]["Actors"] = item[3]

     else

        item.delete("Actor")
        hash["Actor"] = item

        item["Actor"]["Name"] == item[1]
        item["Actor"]["Age"] == item[2]
        item["Actor"]["Filmography"] == item[3]

     end

   end

  return hash

end

testarr = [["Actor", "Morgan", "33", ["dsfds","dsfdsf"]],
  ["Movie", "Titanic", "44", ["dsfds","dfdsf"]],
  ["Actor", "Jack Black", "333", ["ssdsfds","dsfdsf"]]]

puts hashing(testarr)

But it gives me an error for putting the array item into "Movie" and "Actor" then trying to create keys like "Name" and "Age".

How can I make this as I desire?

Upvotes: 1

Views: 108

Answers (6)

Cary Swoveland
Cary Swoveland

Reputation: 110675

Code

def convert(arr, keys)
  arr.group_by(&:first).transform_values do |a|
    a.map { |key, *values| keys[key].zip(values).to_h }
  end
end

Example (using testarr defined in the question)

keys = { "Actor"=>[:name, :Age, :Films], "Movie"=>[:Title, :Gross, :Actors] }

convert(testarr, keys)
  #=> { "Actor"=>[
  #       {:name=>"Morgan", :Age=>"33", :Films=>["A", "B"]},
  #       {:name=>"Jack Black", :Age=>"333", :Films=>["A", "A"]}
  #     ],
  #     "Movie"=>[
  #      {:Title=>"Titanic", :Gross=>"44", :Actors=>["A", "A"]}
  #     ]
  #   }

Explanation

See Enumerable#group_by, Hash#transform_values, Array#zip and Array#to_h.

The steps are as follows.

h = testarr.group_by(&:first)
  #=> { "Actor"=>[
  #       ["Actor", "Morgan", "33", ["A", "B"]],
  #       ["Actor", "Jack Black", "333", ["A", "A"]]
  #     ],
  #     "Movie"=>[
  #       ["Movie", "Titanic", "44", ["A", "A"]]
  #     ]
  #   }

Though not quite equivalent, you can think of testarr.group_by(&:first) as being "shorthand" for testarr.group_by { |a| a.first }. Continuing,

e0 = h.transform_values
  #=> #<Enumerator:
  #   {"Actor"=>[["Actor", "Morgan", "33", ["A", "B"]],
  #              ["Actor", "Jack Black", "333", ["A", "A"]]],
  #    "Movie"=>[["Movie", "Titanic", "44", ["A", "A"]]]}
  #  :transform_values>

The first element is generated by the enumerator e0, passed to the block and the block variable is set equal to that value.

a = e0.next
  #=> [["Actor", "Morgan", "33", ["A", "B"]],
  #    ["Actor", "Jack Black", "333", ["A", "A"]]]

A second enumerator is now created.

e1 = a.map
  #=> #<Enumerator: [["Actor", "Morgan", "33", ["A", "B"]],
  #                  ["Actor", "Jack Black", "333", ["A", "A"]]]:map>

The first value is generated by e1, passed to the inner block and the block variables are assigned values (using disambiguation).

key, *values = e1.next
  #=> ["Actor", "Morgan", "33", ["A", "B"]]
key
  #=> "Actor"
values
  #=> ["Morgan", "33", ["A", "B"]]

The inner block calculation is now performed.

b = keys[key].zip(values)
  #=> keys["Actor"].zip(["Morgan", "33", ["A", "B"]])
  #=> [:name, :Age, :Films].zip(["Morgan", "33", ["A", "B"]])
  #=> [[:name, "Morgan"], [:Age, "33"], [:Films, ["A", "B"]]]
b.to_h
  #=> {:name=>"Morgan", :Age=>"33", :Films=>["A", "B"]}

Now the second and last element is generated by e1 and the same calculations are performed.

key, *values = e1.next
  #=> ["Actor", "Jack Black", "333", ["A", "A"]]
b = keys[key].zip(values)
  #=> [[:name, "Jack Black"], [:Age, "333"], [:Films, ["A", "A"]]]
b.to_h
  #=> {:name=>"Jack Black", :Age=>"333", :Films=>["A", "A"]}

When another value is sought from e1 we obtain the following.

e1.next
  #=> StopIteration: iteration reached an end

This exception is caught, causing e1 to return to the outer block. At this point e0 generates it next (and last value).

a = e0.next
  #=> [["Movie", "Titanic", "44", ["A", "A"]]]

The remaining calculations are similar.

Upvotes: 0

Freddy Kim
Freddy Kim

Reputation: 11

I tried to keep the example you wrote.

First of all, it must be shaped for Array(such as [a, b] ) not Hash( {a, b} ) list of items

# You may want result like this ...
{
    "Actor": [    # not '{' but '['
        {
            "name": "Morgan",
            "Age": "33",
            "Films": ["A", "B"]    # not '{' but '[' also
        },
        {
            "name": "Jack Black",
            "Age": "44",
            "Films": ["A", "A"]
        }
    ],
    "Movie": [
        {
            "Title": "Titanic",
            "Gross": "44",
            "Actors": ["A", "A"]
        }
    ]
}

and then your function should be like this ...

def hashing(arr)
    hash = Hash.new
    hash["Movie"], hash["Actor"] = [], []

    arr.each do |item|

        if item[0] == "Movie"
            movie = {}
            movie["Title"]      = item[1]
            movie["Box Office"] = item[2]
            movie["Actors"]     = item[3]

            item.delete("Movie")         # optional
            hash["Movie"] << movie

        else
            actor = {}
            actor["Name"]           = item[1]
            actor["Age"]            = item[2]
            actor["Filmography"]    = item[3]

            item.delete("Actor")         # optional
            hash["Actor"] << actor
        end

    end

    return hash
end

Then it's time to test! as your codes,

testarr = [
    ["Actor", "Morgan", "33", ["dsfds","dsfdsf"]],
    ["Movie", "Titanic", "44", ["dsfds","dfdsf"]],
    ["Actor", "Jack Black", "333", ["ssdsfds","dsfdsf"]]
]

puts hashing(testarr)

It will return this:

{
  "Movie"=>
    [
      {"Title"=>"Titanic", "Box Office"=>"44", "Actors"=>["dsfds", "dfdsf"]}
    ],
  "Actor"=>
    [
      {"Name"=>"Morgan", "Age"=>"33", "Filmography"=>["dsfds", "dsfdsf"]},
      {"Name"=>"Jack Black", "Age"=>"333", "Filmography"=>["ssdsfds", "dsfdsf"]}
    ]
}

Upvotes: 1

Stephen M
Stephen M

Reputation: 181

testarr = [["Actor", "Morgan", "33", ["A","B"]],
  ["Movie", "Titanic", "44", ["A","A"]],
  ["Actor", "Jack Black", "333", ["A","A"]]]

  a = Hash.new{ |h,k| h[k] = [] }

  testarr.each do |arr|
    b = {name: arr[1], age: arr[2], films: arr[3]}
    a[arr[0]] << b
  end

this will produce

{"Actor"=>[{"name"=>"Morgan", "age"=>"33", "films"=>["A", "B"]}, {"name"=>"Jack Black", "age"=>"333", "films"=>["A", "A"]}], "Movie"=>[{"name"=>"Titanic", "age"=>"44", "films"=>["A", "A"]}]}

Upvotes: 1

Dan Kreiger
Dan Kreiger

Reputation: 5516

The value in your :actor contains a hash without a key. The best thing you can do is put that into an array.

This will work. There might be a cleaner way, but I'm not sure how at the moment:

h = Hash.new { |hash, key| hash[key] = [] }
testarr = [["Actor", "Morgan", "33", ["A", "B"]], ["Movie", "Titanic", "44", ["A", "A"]], ["Actor", "Jack Black", "333", ["A", "A"]]]

testarr.each do |t|
  if t[0] == 'Movie'
    h[t[0]] << {title: t[1], gross: t[2], actors: t[3]}
  else
    h[t[0]] << {name: t[1], age: t[2], films: t[3]}
  end
end

puts h

Output:

{"Actor"=>[{:name=>"Morgan", :age=>"33", :films=>["A", "B"]}, {:name=>"Jack Black", :age=>"333", :films=>["A", "A"]}], "Movie"=>[{:title=>"Titanic", :gross=>"44", :actors=>["A", "A"]}]}

Upvotes: 1

31piy
31piy

Reputation: 23859

You need to iterate through the array and parse each item, appending it to the resultant hash.

testarr = [["Actor", "Morgan", "33", ["A", "B"]],
           ["Movie", "Titanic", "44", ["A", "A"]],
           ["Actor", "Jack Black", "333", ["A", "A"]]]

results = {}

testarr.each do |item|
  key, a, b, c = item
  r = if key == 'Actor'
        { name: a, age: b, movies: c }
      elsif key == 'Movie'
        { title: a, gross: b, actors: c }
      end
  results[key] = [] unless results[key]
  results[key] << r
end

puts results

This will produce:

{"Actor"=>[{:name=>"Morgan", :age=>"33", :movies=>["A", "B"]}, {:name=>"Jack Black", :age=>"333", :movies=>["A", "A"]}], "Movie"=>[{:title=>"Titanic", :gross=>"44", :actors=>["A", "A"]}]}

Upvotes: 1

Mohanraj
Mohanraj

Reputation: 4200

Please try the below code,

v = [["Actor", "Morgan", "33", ["A", "B"]], ["Movie", "Titanic", "44", ["A", "A"]], ["Actor", "Jack Black", "333", ["A", "A"]]]

v.inject({}) do |ot, arr|
  item = {name: arr[1], age: arr[2], films: arr[3]}
  if ot[arr[0]].present?
    ot[arr[0]] << item
  else
    ot[arr[0]] = []
    ot[arr[0]] << item
  end
  ot
end

And the o/p is like below,

# => {"Actor"=>[{:name=>"Morgan", :age=>"33", :films=>["A", "B"]}, {:name=>"Jack Black", :age=>"333", :films=>["A", "A"]}], "Movie"=>[{:name=>"Titanic", :age=>"44", :films=>["A", "A"]}]}

Please note here the Actor is not hash of hashes, it's array of hashes, this is the standard way of keeping collection and convert it to json if you need by using to_json method.

Upvotes: 1

Related Questions