Petr
Petr

Reputation: 2099

Retrieving data from a JSON object in Ruby

I have an object:

EURO_COUNTRIES = ['France', 'Germany', 'Spain']
fruit_production = {
  cuba: {
  #meaning: country c produces k kilograms of fruit in region r for season s
    winter: [{north: 1}, {south: nil}, {east: 4}, {west: 4}],
    summer: [{north: nil}, {south: 5}, {east: ""}, {west: 5}],
  },
  spain: {
    fall: [{north: 7}, {}],
    summer: [{north: nil}, {south: "5"}, {east: 2}, {west: '5'}],
    spring: []
  }
#and the list goes on for hundreds of countries
}fruit_production = {
  cuba: {
  #meaning: country c produces k kilograms of fruit in region r for season s
    winter: [{north: 1}, {south: nil}, {east: 4}, {west: 4}],
    summer: [{north: nil}, {south: 5}, {east: ""}, {west: 5}],
  },
  spain: {
    fall: [{north: 7}, {}],
    summer: [{north: nil}, {south: "5"}, {east: 2}, {west: '5'}],
    spring: []
  }
#and the list goes on for hundreds of countries
}

I tried to convert it to JSON object with json.parse(fruit_production), but how can I can actually get the data from it and loop it after that? For example:

  1. Return the country with the highest year-round fruit yield 
  2. Return the European country with the highest warm season (spring, summer) fruit yield
  3. Return a mapping from country to total yearly yield, i.e {netherlands: 1818, thailand: 8200, etc...}
  4. Return a mapping of the total worldwide yields per region, e.g {north: 28333, south: 91339, east: 14343, west: 50290}

Upvotes: 1

Views: 448

Answers (2)

Nabeel
Nabeel

Reputation: 2302

To get you started off, it's late and I'm sure I've missed something. There are plenty of ways going about this as well.

data = fruit_production.each_with_object(Hash.new {|k,v| k[v] = Hash.new(0)}) do |(country, seasons), memo|
  seasons.each do |season, regions|
    regions.each do |region|
      fruit_yield = Integer(region.values.first) rescue 0
      memo[:total_highest_profit][country] += fruit_yield
      memo[:total_region_yield][region.keys.first] += fruit_yield if region.keys.first
      memo[:total_warm_season][country] += fruit_yield if season == :summer || season == :spring
    end
  end
end
# => {
#     :total_region_yield=>{:north=>8, :south=>10, :east=>6, :west=>14},
#     :total_highest_profit=>{:cuba=>19, :spain=>19}, 
#     :total_warm_season=>{:cuba=>10, :spain=>12}
#    }

You can get whatever you want from this data, such as highest country or European country (for this you'll have to use array#include?).

data[:total_highest_profit].max_by {|_, v| v}
# => [:cuba, 19]

Upvotes: 1

Fred Willmore
Fred Willmore

Reputation: 4604

you can convert to json with to_json

>   fruit_production.to_json
=> "{\"cuba\":{\"winter\":[{\"north\":1},{\"south\":null},{\"east\":4},{\"west\":4}],\"summer\":[{\"north\":null},{\"south\":5},{\"east\":\"\"},{\"west\":5}]},\"spain\":{\"fall\":[{\"north\":7},{}],\"summer\":[{\"north\":null},{\"south\":\"5\"},{\"east\":2},{\"west\":\"5\"}],\"spring\":[]}}"

As for retrieving the info from a json string, I think you're better off just to convert it back to a hash and work with that.

1, 3) You can get the yearly yield for each country by summing over season and region and taking the max.

note: your hash of yield by region is unnecessarily complex - you have an array of single-element hashes instead of a single hash indexed by region.

current: [{:north=>nil}, {:south=>"5"}, {:east=>2}, {:west=>"5"}]

better: {:north=>nil, :south=>"5", :east=>2, :west=>"5"}

However, this will work with what you've got, though I'm sure it can be simplified, (especially if you take my recommendation on the region-production structure - you can get rid of the sometimes-confusing inject function and just sum on the values of the hash):

by_country = Hash[fruit_production.map { |country, production| [country, production.map {|season, data| data.inject(0) { |sum, v| sum + v.values.map(&:to_i).sum } }.sum]}]

=> {:cuba=>19, :spain=>19}

Uh oh, you have a tie! I don't know what you want to do with that case, but you can just select one max pretty easily:

by_country.max_by { |k,v| v }
=> [:cuba, 19]

2) You can get a subset of fruit_production for European countries by selecting the elements of fruit_production whose key (after some string manipulation) matches one of the country names in the list:

 euro_fruit_production = fruit_production.select {|k,v| EURO_COUNTRIES.include?(k.to_s.titleize)}

=> {:spain=>{:fall=>[{:north=>7}, {}], :summer=>[{:north=>nil}, {:south=>"5"}, {:east=>2}, {:west=>"5"}], :spring=>[]}}

you can use that to work out seasonal totals. Good luck with the rest!

Upvotes: 1

Related Questions