0

I am a bit lost on how to format this array of objects.

Initial array:

cars = [{model: 'nissan', type: 'wingroad'},{model: 'nissan', type: 'sunny'},{model: 'nissan', type: 'terrano'},{model: 'toyota', type: 'hilux'}]

Expected Output:

formatted_cars = [{name: 'nissan', value: ['wingroad', 'sunny', 'terrano']}, {name: 'toyota', value: ['hilux']

Would I map over the results and apply an inject then apply some cunning merge techniques for that formatted array. Or is there another way to go about it?

I am new to ruby and would love any help, thanks :)

John
  • 43
  • 1
  • 5
  • These other stackoverflow questions should get you started: https://stackoverflow.com/questions/27306879/merging-hash-values-in-an-array-of-hashes-based-on-key , https://stackoverflow.com/questions/24233493/merge-duplicates-in-array-of-hashes , https://stackoverflow.com/questions/2871323/ruby-merge-two-hash-as-one-and-with-value-connected , https://stackoverflow.com/questions/24916479/rails-ruby-merge-two-hashes-with-same-key-different-values – engineersmnky Apr 24 '18 at 20:17
  • From a quick glance you can use `Enumerable#reduce` for this task: https://ruby-doc.org/core-2.2.3/Enumerable.html#method-i-reduce – posixpascal Apr 24 '18 at 20:18
  • Hi, yes that was a mistake. I have corrected the formatted cars array in the question to have the correct values. Thanks :) – John Apr 24 '18 at 22:16
  • formatted_cars is still not valid ruby value. – Wand Maker Apr 25 '18 at 03:53

3 Answers3

4

There are many ways to do this. Here is one;

cars = [
  {model: 'nissan', type: 'wingroad'},
  {model: 'nissan', type: 'sunny'},
  {model: 'nissan', type: 'terrano'},
  {model: 'toyota', type: 'hilux'}
]

cars
  .group_by { |car| car[:model] }
  .map { |model, cars| {name: model, value: cars.map { |car| car[:type] }} }

...However, why are you starting with such an odd data format, and aiming to finish with another odd data format? (By "odd", I basically mean relying on an array of hashes of arrays to store the data.)

There may be a good reason for this (e.g. integration with a 3rd party API), but otherwise I would suggest making this somehow more object-oriented and using classes to define the make/model of the cars. For example, perhaps something like:

# e.g. Nissan
class CarMake
  attr_reader :name, :models
  def initialize(name)
    @name = name
    @models = []
  end

  def add_model(name)
    model = CarModel.new(name)
    @models << model
    model.make = self
  end
end

# e.g. WingRoad
class CarModel
  attr_reader :name
  attr_accessor :make
  def initialize(name)
    @name = name
  end
end

# Assuming we still need to start with this data structure!
cars = [
  {model: 'nissan', type: 'wingroad'},
  {model: 'nissan', type: 'sunny'},
  {model: 'nissan', type: 'terrano'},
  {model: 'toyota', type: 'hilux'}
]

car_makes = {}
cars.each do |car|
  car_makes[car[:model]] ||= CarMake.new(car[:model])
  car_makes[car[:model]].add_model(car[:type])
end

This is just one of many possible ways to organise the code, and although it may be a little more complex to understand at first, the resulting data structure is much more useful:

car_makes
=> {"nissan"=>
  #<CarMake:0x00007ff2ee44ad20
   @models=
    [#<CarModel:0x00007ff2ee44aca8 @make=#<CarMake:0x00007ff2ee44ad20 ...>, @name="wingroad">,
     #<CarModel:0x00007ff2ee44ac80 @make=#<CarMake:0x00007ff2ee44ad20 ...>, @name="sunny">,
     #<CarModel:0x00007ff2ee44ac58 @make=#<CarMake:0x00007ff2ee44ad20 ...>, @name="terrano">],
   @name="nissan">,
 "toyota"=>#<CarMake:0x00007ff2ee44ac30 @models=[#<CarModel:0x00007ff2ee44ab68 @make=#<CarMake:0x00007ff2ee44ac30 ...>, @name="hilux">], @name="toyota">}

car_makes['nissan'].models
=> [#<CarModel:0x00007ff2ee44aca8 @make=#<CarMake:0x00007ff2ee44ad20 @models=[...], @name="nissan">, @name="wingroad">,
 #<CarModel:0x00007ff2ee44ac80 @make=#<CarMake:0x00007ff2ee44ad20 @models=[...], @name="nissan">, @name="sunny">,
 #<CarModel:0x00007ff2ee44ac58 @make=#<CarMake:0x00007ff2ee44ad20 @models=[...], @name="nissan">, @name="terrano">]

car_makes['nissan'].models.first
=> #<CarModel:0x00007ff2ee44aca8
 @make=
  #<CarMake:0x00007ff2ee44ad20
   @models=
    [#<CarModel:0x00007ff2ee44aca8 ...>,
     #<CarModel:0x00007ff2ee44ac80 @make=#<CarMake:0x00007ff2ee44ad20 ...>, @name="sunny">,
     #<CarModel:0x00007ff2ee44ac58 @make=#<CarMake:0x00007ff2ee44ad20 ...>, @name="terrano">],
   @name="nissan">,
 @name="wingroad">

car_makes['nissan'].models.first.make
=> #<CarMake:0x00007ff2ee44ad20
 @models=
  [#<CarModel:0x00007ff2ee44aca8 @make=#<CarMake:0x00007ff2ee44ad20 ...>, @name="wingroad">,
   #<CarModel:0x00007ff2ee44ac80 @make=#<CarMake:0x00007ff2ee44ad20 ...>, @name="sunny">,
   #<CarModel:0x00007ff2ee44ac58 @make=#<CarMake:0x00007ff2ee44ad20 ...>, @name="terrano">],
 @name="nissan">

...And so on. We now have a structured data, instead of just arbitrary (and mis-named!) mixes of arrays and hashes that are messy to manipulate.

Tom Lord
  • 27,404
  • 4
  • 50
  • 77
  • Thanks for the answer, yes basically the data type has to be odd because the formatted_cars array is used to populate a a select field in react form. :) – John Apr 24 '18 at 23:04
  • 1
    @John If he has answered your question, please consider accepting his answer, there is tic in the left top, you need to click, after clicking that button, it will become green colour. – Rajagopalan Apr 25 '18 at 06:24
  • @John I would still suggest using some well-structured model like the above, and generating the necessary "React JSON" format as a separate method. If this is a rails application, you could do this via a standard serializer; or if we're doing this in plain ruby objects then how about: `class CarMake; def to_json; {name: name, value: models.map(&:name)}; end; end` ... Although my point still stands that you've got a rather muddled up naming convention between `model` (should be "make"!), `type` (should be "model") and `value` (should be "makes"). – Tom Lord Apr 25 '18 at 08:29
0
cars = [{model: 'nissan', type: 'wingroad'},
        {model: 'nissan', type: 'sunny'},
        {model: 'nissan', type: 'terrano'},
        {model: 'toyota', type: 'hilux'}]

cars.each_with_object({}) { |g,h| (h[g[:model]] ||= []) << g[:type] }.
     map { |k,v| { model:k, type:v } }
  #=> [{:model=>"nissan", :type=>["wingroad", "sunny", "terrano"]},
  #    {:model=>"toyota", :type=>["hilux"]}]

Note that

cars.each_with_object({}) { |g,h| (h[g[:model]] ||= []) << g[:type] }
  #=> {"nissan"=>["wingroad", "sunny", "terrano"], "toyota"=>["hilux"]}
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

Yes, there is, as follows:

cars.group_by{|h| h[:model]}.transform_values{|v| v.map{|h| h[:type]}}
#=> {"nissan"=>["wingroad", "sunny", "terrano"], "toyota"=>["hilux"]}
sawa
  • 165,429
  • 45
  • 277
  • 381
  • 1
    Please make this a full answer, not just a code answer with no explanation. – petezurich Apr 25 '18 at 06:25
  • Thanks for the attempt but the answer is not returned in the correct format, it is supposed to be nested in an array. [{name: 'nissan', value: ['wingroad', 'sunny', 'terrano']}, {name: 'toyota', value: ['hilux']}] – John Apr 29 '18 at 21:13