1

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?

6 Answers 6

1
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"]}]}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. How can I actually put Age and Filmography of an Actor inside the name key? Like Name => Morgan => { Age: 33, Filmo = [...] }
1

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.

3 Comments

Thanks! But I get a undefined method `present?' for nil:NilClass (NoMethodError). Is that a valid method?
Yes, it;s rails standard method. Are you using Rails or not?
I'm using regular ruby
1

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"]}]}

Comments

1

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"]}]}

1 Comment

Thanks. How can I actually put Age and Filmography of an Actor inside the name key? Like Name => Morgan => { Age: 33, Filmo = [...] }
1

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"]}
    ]
}

2 Comments

Thanks. How can I actually put Age and Filmography of an Actor inside the name key? Like Name => Morgan => { Age: 33, Filmo = [...] }
Simple! Wrap your structure once more. in your expression, Name => Morgan => {...} goes with wrapper like Name => { Morgan => {...} }. All of your keys in structure, must have whole under structure for value such like what i wrap. By the way, i suggest original one rather than new one for protecting and defining objects more clearly.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.