Improvements in code having iteration of nested hashes and arrays of input - ruby

I could use any improvements to improve my code. I think most of the method have the same layout but I am not getting the desired output, so any help would be great. If you want to see the exercise online, its called the Bachelor Nested Iteration. I really have no clue why I am not getting my desired output, to me my working out makes sense I guess.
for the get_first_name_of_season_winner method, no matter what arguments I pass through when I call it, I always get "Beth Smalls" as an output when it shouldn't be the case. If I pass "Season 29", the output should be "Ashley Yeats"
for the get_contestant_name method, it's the same thing. It always returns "Beth Smalls" no matter what occupation I pass through. For example if I call it like this
get_contestant_name(thebachelor, "Chiropractic Assistant" )
it should return "Becca Tilley" as an output but it doesn't.
for the count_contestant_by_hometown, it should return the number of contestants which are from the hometown thats passed in the method, however, no matter which argument I pass, I get the number 4 as an output.
for the get_occupation, it should return the name of the person corresponding to the hometown being passed in the method, but I always get "Beth Smalls" no matter which hometown I pass through it.
The final method, I have no idea how to do it. It takes in two arguments––the data hash and a string of a season. Iterate through the hash and return the average age of all of the contestants for that season.
thebachelor = {
"season 30": [
{
"name": "Beth Smalls",
"age": "26",
"hometown": "Great Falls, Virginia",
"occupation": "Nanny/Freelance Journalist",
"status": "Winner"
},
{
"name": "Becca Tilley",
"age": "27",
"hometown": "Shreveport, Louisiana",
"occupation": "Chiropractic Assistant",
"status": "Eliminated Week 8"
}
],
"season 29": [
{
"name": "Ashley Yeats",
"age": "24",
"hometown": "Denver, Colorado",
"occupation": "Dental Assitant",
"status": "Winner"
},
{
"name": "Sam Grover",
"age": "29",
"hometown": "New York, New York",
"occupation": "Entertainer",
"status": "Eliminated Week 6"
}
]
}
Now the methods. get_first_name_of_season_winner is
def get_first_name_of_season_winner(data, season)
#this method returns the first name of that seasons winner
#pass the season of the show, and then it returns only th FIRST NAME of the winner for that season
#iterate through the inital hash to access the season number
#then iterate through the array, to access the hash inside
#acess the "status" to get the output
data.each do |season, contestant_Data|
contestant_Data.each do |a|
a.each do |attribute, value|
if value == "Winner"
return a[:name]
end
end
end
end
end
get_first_name_of_season_winner(thebachelor, "season 29") #returns the full name of only "Beth Smalls"
get_contestant_name is:
def get_contestant_name(data, occupation) #this method takes in the data hash and an occupation string and returns the name of the woman who has that occupation
#iterate through the initial hash to access the seasons
#iterate through the seasons to access the arrays inside
#access the occupation element of the array
#return the person who has the occupation
data.each do |season, contestant_data|
contestant_data.each do |a|
a.each do |attribute, value|
if attribute == :occupation
return a[:name]
end
end
end
end
end
get_contestant_name(thebachelor, "Chiropractic Assistant" ) #returns the full name of only "Beth Smalls"
count_contestant_by_hometown is:
def count_contestant_by_hometown(data, hometown) #this method should return the number of contestants from the hometown passed
#include a counter variable
#iterate through the hash to access the seasons
#access the array
#access the hometown key in the hash
#keep count
counter = 0
data.each do |season, contestant_data|
contestant_data.each do |a|
a.each do |attribute, value|
if attribute == :hometown
counter += 1
end
end
end
end
return counter
end
count_contestant_by_hometown(thebachelor, "Denver, Colorado") #returns the number 4, I have no idea why
get_occupation is:
def get_occupation(data, hometown) #should return the occupation of of the first contestant who hails from the hometown
data.each do |season, contestant_data|
contestant_data.each do |a|
a.each do |attribute, value|
if attribute == :hometown
return a[:name]
end
end
end
end
end
get_occupation(thebachelor, "Denver, Colorado") #returns "Beth Smalls" when it should return "Ashley Yeats"
average_age_for_season is:
def average_age_for_season(data, season) #returns the average age of all contestants for that season

I think a big problem comes from the data you're passing in. Take for example, a working solution for your final issue.
To get the data for a single season, you can use:
def average_age_for(data, season)
contestants = data[season]
contestants.sum { |contestant| contestant[:age].to_f } / contestants.count
end
average_age_for(thebatchelor, :"season 30")
#=> 26.5
Note that you need to pass :"season 30", rather than simply "season 30". That's because your data is is using symbolised strings as keys, rather than just strings.
Replace your data's keys with strings:
thebachelor = {
"season 30" => [
{
"name" => "Beth Smalls",
"age" => "26",
"hometown" => "Great Falls, Virginia",
"occupation" => "Nanny/Freelance Journalist",
"status" => "Winner"
},
{
"name" => "Becca Tilley",
"age" => "27",
"hometown" => "Shreveport, Louisiana",
"occupation" => "Chiropractic Assistant",
"status" => "Eliminated Week 8"
}
],
"season 29" => [
{
"name" => "Ashley Yeats",
"age" => "24",
"hometown" => "Denver, Colorado",
"occupation" => "Dental Assitant",
"status" => "Winner"
},
{
"name" => "Sam Grover",
"age" => "29",
"hometown" => "New York, New York",
"occupation" => "Entertainer",
"status" => "Eliminated Week 6"
}
]
}
Then look for a string in the method:
def average_age_for(data, season)
contestants = data[season]
# vvvvvvv
contestants.sum { |contestant| contestant["age"].to_f } / contestants.count
# ^^^^^^^
end
And this takes shape.
You can then do:
1)
def get_first_name_of_season_winner(data, season)
data[season].detect { |contestant| contestant["status"] == "Winner" }["name"].split.first
end
get_first_name_of_season_winner(thebachelor, "season 29")
#=> "Ashley"
2)
def get_contestant_name(data, occupation)
data.values.flatten.detect { |contestant| contestant["occupation"] == occupation }
end
get_contestant_name(thebachelor, "Chiropractic Assistant")
#=> {"name"=>"Becca Tilley", "age"=>"27", "hometown"=>"Shreveport, Louisiana", "occupation"=>"Chiropractic Assistant", "status"=>"Eliminated Week 8"}
3)
def count_contestant_by_hometown(data, town)
data.values.flatten.select { |contestant| contestant["hometown"] == town }.count
end
count_contestant_by_hometown(thebachelor, "New York, New York")
#=> 1
4)
def get_occupation(data, hometown)
data.values.flatten.detect { |contestant| contestant["hometown"] == hometown }["occupation"]
end
get_occupation(thebachelor, "New York, New York")
#=> "Entertainer"

Generalised && Optimised Solution:
Following method will work to get whatever you need from your hash thebachelor,
def get_information(data, required, season, optional, hash= {})
data = season.nil? ? data.values.flatten : data[season]
selected = data.select { |x| (hash.inject(true) { |m, (k,v)| m &&= (x[k] == v) }) }
required_data = selected.map { |x| x[required] }
if optional == :average && required == :age
(required_data.map(&:to_i).sum / required_data.count.to_f).round(2)
else
(optional == :count) ? required_data.count : required_data
end
end
Data need to be provided as below,
data - hash input from which you want to retrieve data
required - output inner hash attribute you want in output.
season - If you want to retrieve data season specific, provide season, else nil to get from all season.
Optional - It can be set nil, unless you want count or average. Otherwise pass :count or :average as optional argument.
hash - filter option. If you want to filter inner data by attributes like :hometown or :status or :occupation. Just provide it. otherwise it will set empty hash.
See examples below,
# Get name of season winner for season 'season 29'
get_information(thebachelor, :name, :'season 29', nil, status: 'Winner')
# => ["Ashley Yeats"]
# Get name of all winners irrespective of season
get_information(thebachelor, :name, nil, nil, status: 'Winner')
# => ["Beth Smalls", "Ashley Yeats"]
# Get contestant name for occupation "Chiropractic Assistant"
get_information(thebachelor, :name, nil, nil, occupation: "Chiropractic Assistant")
# => ["Becca Tilley"]
# Count contestant by home town "Denver, Colorado"
get_information(thebachelor, :name, nil, :count, hometown: "Denver, Colorado")
# => 1
# Get occupation of contestant who hails from hometown "Denver, Colorado"
get_information(thebachelor, :occupation, nil, nil, hometown: "Denver, Colorado")
# => ["Dental Assitant"]
# Get Average age for season :"season 29"
get_information(thebachelor, :age, :"season 29", :average)
# => 26.5
This method provide moreover than you asked in your question.

First, I would reformat your data so it's easy to select/detect:
data = data.map { |key, value| value.transform_keys(&:to_sym).merge(season: key) }
so now it looks like
[{
season: "season 30",
name: "Beth Smalls",
age: "26",
hometown: "Great Falls, Virginia",
occupation: "Nanny/Freelance Journalist",
status: "Winner"
},...
]
Now it's much easier to filter and detect:
def get_first_name_of_season_winner(data, season)
p = ->(v) { v[:season] == season && v[:status] == 'Winner' }
data.detect(&p)[:name][/\w+/]
end
def get_contestant_name(data, occupation)
p = ->(v) { v[:occupation] == occupation }
data.detect(&p)[:name]
end
def count_contestant_by_hometown(data, hometown)
p = ->(v) { v[:hometown] = hometown }
data.select(&p).count
end
def get_occupation(data, hometown)
p = ->(v) { v[:hometown] = hometown }
data.detect(&p)[:occupation]
end
def average_age_for_season(data, season)
p = ->(v) { v[:season] = season }
ages = data.select(&p).map { |datum| datum[:age] }
ages.sum.fdiv(ages.count) unless ages.empty?
end
In general, all these problems are of two types:
1. Given an array of data, find all the items that satisfy a certain condition
2. Given an array of data, find the first item that satisfies a certain condition
And you always solve them with select/detect and a block/proc.

Related

get values from a multi array nested hashes ruby

I have a complex multi nested array of hashes like below:
{
"Food":[
{
"id": "01",
"name":"ABC",
"branch":"London",
"platter_cost":"£40.00",
"Kebab":[
{
"name":"chicken",
"value":"£8.12"
},
{
"name":"lamb",
"value":"£9.67"
}
],
"sides":[
{
"type":"drinks",
"options":[
{
"id":1,
"name":"Coke",
"price":"£4.70"
},
{
"id":2,
"name":"Pepsi",
"price":"£2.90"
},
{
"id":3,
"name":"Tango",
"price":"£4.00"
}
]
},
{
"type":"chips",
"options":[
{
"id":4,
"name":"Peri-Peri",
"price":"£4.00"
}
]
}
]
},
{
"id": "02",
"name":"XYZ",
"branch":"Manchester",
"platter_cost":"£30.00",
"Kebab":[
{
"name":"chicken",
"value":"£5.22"
},
{
"name":"lamb",
"value":"£6.35"
}
],
"sides":[
{
"type":"drinks",
"options":[
{
"id":77,
"name":"coke",
"price":"£3.70"
},
{
"id":51,
"name":"Orange",
"price":"£4.00"
},
{
"id":33,
"name":"Apple",
"price":"£2.00"
}
]
},
{
"type":"chips",
"options":[
{
"id":20,
"name":"peri-peri",
"price":"£4.00"
},
{
"id":18,
"name":"cheesy",
"price":"£3.50"
}
]
}
]
}
]
}
I have a method to return a cost value based on the arguments. Example:
def total_cost(id: "01", options: [1, 4], kebab: 'chicken')
platter_cost + (sum of)options + cost of chicken kebab
end
Arguments explanation:
First argument: id is the main id(company_id),
Second argument: options: [1, 4]. 1 and 4 are the id's inside the Side options, The ids are unique so it doesn't matter the options are chips or drinks.
Third argument: is the cost of the chicken kebab.
So the output for the id: "01" is £16.82. coke_cost + tango_cost + chicken_kebab_cost
what is the clean and efficient way to get the results?
So far I tried the below but am a bit lost on which way to choose. Thanks in advance.
def dishes
file = File.read('food.json')
obj = JSON.parse(file)
obj['food']
end
def self_hash # Trying to create a single hash like an active record object
h = {}
dishes.each do |dish|
h["id"] = dish["id"]
h["platter_cost"] = dish["platter_cost"]
h["kebab"] = dish["kebab"].each{ |k| {"chicken: #{k["chicken"]}", "lamb: #{k["lamb"]}"} } # Not working
end
end
This is an awkward data structure to work with. It's unfortunate it can't be changed, but we can do things to make it easier to work with.
First, turn it into a class so we have something to hang behavior off of.
class Dishes
attr_reader :dishes
def initialize(dishes)
#dishes = dishes
end
Now we need to get the right pieces of dishes. Unfortunately dishes is poorly designed. We can't just do dishes[id] we need to search through Arrays for matches. With a class we can write methods to abstract away working with this awkward data structure.
Let's abstract away having to dig into the Food key every time.
def menus
#dishes.fetch(:Food)
end
Note that it's the Symbol :Food, not the string "Food". "Food":[...] produces a Symbol.
Note that I'm using fetch because unlike [] it will throw a KeyError if Food is not found. This makes error handling much easier. I'll be using fetch consistently through the code.
Also note that the method is called menus because this appears to be a better description of what dishes["Food"] is: a list of menus for various locations.
Now we can search menus for a matching id using Enumerable#find. Again, we abstract this away in a method.
def menu(id)
menu = menus.find { |m| m.fetch(:id) == id }
raise "Can't find menu id #{id}" if !menu
return Menu.new(menu)
end
Not only is finding a menu abstracted away, but we also have proper error handling if we can't find it.
Now that we've found the menu we want, we can ignore the rest of the data structure. We have a Menu class just for working with the menu.
class Menu
attr_reader :menu
def initialize(menu)
#menu = menu
end
We can now fetch the kebabs. Searching an Array is awkward. Let's turn it into a more useful Hash keyed on the name of the kebab.
# Turn the list of kebabs into a hash keyed on
# the name. Cache the result.
def kebabs
#kebabs ||= menu.fetch(:Kebab).each_with_object({}) { |k,h|
h[ k[:name] ] = k
}
end
Now we can search the Hash of kebabs for matching names using Hash#fetch_values. Note it's names because someone might want to order more than one delicious kebab.
def find_kebabs(names = [])
kebabs.fetch_values(*names)
end
An advantage of this approach is we'll get a KeyError if a kebab does not exist.
Like with the kebabs, we want to turn all the sides into one hash keyed on the ID. Getting all the sides is a bit tricky. They're broken up into several different Arrays. We can use flat_map to flatten the sides into one Array.
def sides
# Flatten out the list of sides into one Array.
# Then turn it into a Hash keyed on the ID
#sides ||= menu.fetch(:sides).flat_map { |types|
types.fetch(:options)
}.each_with_object({}) { |s,h|
h[ s[:id] ] = s
}
end
Now that it's flattened we can search the Hash just like we did with kebabs.
def find_sides(ids = [])
sides.fetch_values(*ids)
end
Now that we have these methods we can find the sides and kebabs. Again, the data structure is working against us. The price is in a string with a £. If we want to total up the prices we need to turn "£4.00" into 4.00
def price_to_f(price)
price.gsub(/^\D*/, '').to_f
end
And where the price is stored is inconsistent. For kebabs it's value and for sides its price. More methods to smooth this over.
def side_price(side)
price_to_f(side.fetch(:price))
end
def kebab_price(kebab)
price_to_f(kebab.fetch(:value))
end
(Note: Kebab and Side could be their own classes with their own price methods)
Finally we can put it all together. Find the items and sum their prices.
def price(kebabs:[], sides:[])
price = find_kebabs(kebabs).sum { |k| kebab_price(k) }
price += find_sides(sides).sum { |s| side_price(s) }
return price
end
It would look like so.
dishes = Dishes.new(data)
menu = dishes.menu("01")
p menu.price(kebabs: ["chicken"], sides: [1,3])
If any kebabs or sides are not found you get a KeyError.
menu.price(kebabs: ["chicken"], sides: [1,398,3])
test.rb:149:in `fetch_values': key not found: 398 (KeyError)
We can make the error handling a bit more robust by writing up some custom KeyError exceptions.
class Menu
class SideNotFoundError < KeyError
def message
#message ||= "Side not found: #{key}"
end
end
class KebabNotFoundError < KeyError
def message
#message ||= "Kebab not found: #{key}"
end
end
end
Then we can modify our finder methods to throw these exceptions instead of a generic KeyError.
def find_sides(ids = [])
sides.fetch_values(*ids)
rescue KeyError => e
raise SideNotFoundError, key: e.key
end
def find_kebabs(names = [])
kebabs.fetch_values(*names)
rescue KeyError => e
raise KebabNotFoundError, key: e.key
end
These more specific errors allow for more robust error handling while maintaining the Menu black box.
begin
price = menu.price(kebabs: ["chicken"], sides: [1,398,3])
# more code that depends on having a price
rescue Menu::KebabNotFoundError => e
# do something when a kabab is not found
rescue Menu::SideNotFoundError => e
# do something when a side is not found
end
This might seem like overkill, I'm sure someone can come up with some clever compressed code. It's worth it. I work with awkward and inconsistent data structures all the time; a class makes working with them much easier in the long run.
It breaks the problem down into small pieces. These pieces can then be unit tested, documented, given robust error handling, and used to build more functionality.
Here it is all spelled out.
class Dishes
attr_reader :dishes
def initialize(dishes)
#dishes = dishes
end
def menus
dishes.fetch(:Food)
end
def menu(id)
menu = menus.find { |m| m[:id] == id }
raise "Can't find menu id #{id}" if !menu
return Menu.new(menu)
end
end
class Menu
attr_reader :menu
def initialize(menu)
#menu = menu
end
def sides
# Flatten out the list of sides and turn it into
# a Hash keyed on the ID.
#sides ||= menu.fetch(:sides).flat_map { |types|
types.fetch(:options)
}.each_with_object({}) { |s,h|
h[ s[:id] ] = s
}
end
# Turn the list of kebabs into a hash keyed on
# the name.
def kebabs
#kebabs ||= menu.fetch(:Kebab).each_with_object({}) { |k,h|
h[ k[:name] ] = k
}
end
def find_sides(ids = [])
sides.fetch_values(*ids)
rescue KeyError => e
raise SideNotFoundError, key: e.key
end
def find_kebabs(names = [])
kebabs.fetch_values(*names)
rescue KeyError => e
raise KebabNotFoundError, key: e.key
end
def price_to_f(price)
price.gsub(/^\D*/, '').to_f
end
def side_price(side)
price_to_f(side.fetch(:price))
end
def kebab_price(kebab)
price_to_f(kebab.fetch(:value))
end
def price(kebabs:[], sides:[])
price = find_kebabs(kebabs).sum { |k| kebab_price(k) }
price += find_sides(sides).sum { |s| side_price(s) }
return price
end
class SideNotFoundError < KeyError
def message
#message ||= "Side not found: #{key}"
end
end
class KebabNotFoundError < KeyError
def message
#message ||= "Kebab not found: #{key}"
end
end
end
def total_cost(h, id:, options:, kebab:)
g = h[:Food].find { |g| g[:id] == id }
g[:Kebab].find { |f| f[:name] == kebab }[:value][1..-1].to_f +
g[:sides].sum do |f|
f[:options].sum { |f| options.include?(f[:id]) ? f[:price][1..-1].to_f : 0 }
end
end
total_cost(h, id: "01", options: [1, 3], kebab: 'chicken')
#=> 16.82
total_cost(h, id: "01", options: [1, 3, 4], kebab: 'chicken')
#=> 20.82
The first step results in
g #=> {:id=>"01", :name=>"ABC", :branch=>"London", :platter_cost=>"£40.00",
# :Kebab=>[{:name=>"chicken", :value=>"£8.12"},
# {:terms=>"lamb", :value=>"£9.67"}],
# :sides=>[{:type=>"drinks",
# :options=>[
# {:id=>1, :name=>"Coke", :price=>"£4.70"},
# {:id=>2, :name=>"Pepsi", :price=>"£2.90"},
# {:id=>3, :name=>"Tango", :price=>"£4.00"}
# ]
# },
# {:type=>"chips",
# :options=>[
# {:id=>4, :name=>"Peri-Peri", :price=>"£4.00"}
# ]
# }
# ]
# }
Note: [].sum #=> 0.

This for loop is unwilling to pass, no method error

If I have:
#pet_shop = {
pets: [
{
name: "Sir Percy",
pet_type: :cat,
breed: "British Shorthair",
price: 500
},
{
name: "King Bagdemagus",
pet_type: :cat,
breed: "British Shorthair",
price: 500
}
]
}
I need a function that returns the pet if I give it the "name", so I have:
def find_pet_by_name(pet_shop, name)
for pet in pet_shop[:pets]
if pet[:name] == name
return pet
else return nil
end
end
end
But this is not passing the test. I get:
NoMethodError: undefined method `[]' for nil:NilClass
This is such a simple function, I do not know why it doesn't pass.
The problem with your code is that you do not complete the loop. You return either pet or nil for the first value of pet, namely, the "Sir Percy" hash. Your code should be as follows.
def find_pet_by_name(pet_shop, name)
for pet in pet_shop[:pets]
return pet if pet[:name] == name
end
nil
end
find_pet_by_name #pet_shop, "King Bagdemagus"
#=> {:name=>"King Bagdemagus", :pet_type=>:cat, :breed=>"British Shorthair",
# :price=>500}
I would suggest you instead use the method Enumerable#find.
name = "King Bagdemagus"
#pet_shop[:pets].find { |h| h[:name] == name }
#=> {:name=>"King Bagdemagus", :pet_type=>:cat, :breed=>"British Shorthair",
# :price=>500}
name = "Queen Bagdemagus"
#pet_shop[:pets].find { |h| h[:name] == name }
#=> nil
Incidentally, Rubiests rarely use for loops. (I've never used one). It's better to use an enumerator, such as each, find, select, reduce, in part because the value of the iteration variable (here pet) is not visible outside an enumerator's block.

How can I shrink and make a better use of this method? - RUBY (raw)

Working on a project and trying to turn this method (I Have some more similar methods like that in my project) into a more dynamic and concise way
Data from image
def proficiency_parser(stored_data, name, race, year, title, percentage)
if stored_data.has_key?(name)
if stored_data[name].has_key?(race)
if stored_data[name][race].has_key?(year)
stored_data[name][race][year][title] = percentage
else
stored_data[name][race][year] = {title => percentage}
end
else
stored_data[name][race] = {year => {title => percentage}}
end
else
stored_data[name] = {race => {year => {title => percentage}}}
end
end
so essentially this method through my data to identify whether it meets so of those specification showing in the code, essentially I just don't want to use this amount of "elses" and "Ifs" if at all possible.
Data
stored_data
# => {"COLORADO"=>{3=>{2008=>{:math=>0.697}}}}
name
# => "COLORADO"
race
# => 3
year
# => 2008
title
# => :math
percentage
# => 0.697
Take a look at Hash#dig which is included in Ruby versions 2.3.0 or newer.
To summarize:
hash_1 = { a: { a: { a: "b" } } }
hash_2 = { c: { c: { c: "d" } } }
hash_1.dig(:a, :a, :a) # returns "b"
hash_2.dig(:a, :a, :a) # returns nil
So you could say if hash_1.dig(:a, :a) instead of
if hash_1[:a]
if hash_1[:a][:a]
# etc
There's also another way to do it, which is to rescue your NoMethod [] errors.
Here's an example of that:
if hash_1[:a][:a][:a] rescue false
puts "the key exists"
else
puts "the key doesnt exist"
end
You can use some recursive call
Input data
stored_data = {}
name = 'COLORADO'
race = 3
year = 2008
title = :math
percentage = 0.697
Methods
def proficiency_parser(stored_data, name, race, year, title, percentage)
parser(stored_data, name, {race => {year => {title => percentage}}})
end
def parser(data, key, value)
data[key] ? value.each { |k, v| parser(data[key], k, v) } : data[key] = value
end
call
proficiency_parser(stored_data, name, race, year, title, percentage)
p stored_data
# => {"COLORADO"=>{3=>{2008=>{:math=>0.697}}}}
I hope this helps

Ruby map a hash and extract the keys with values if nested or not

I’m using the PaperTrail gem to track changes in my Job model. Each time I change a record, the object is serialized and stored inside a changeset field on my Version model.
To display the values that have changed, I’m deserializing the object and iterating through it using map. I need help creating a method that extracts all the keys and values from this hash, regardless of whether they’re nested or not. The nesting will never be more than one level deep.
Here is my current code:
# deserializing the object into a hash
json_string = v.changeset.to_json
serialized_json = JSON.parse(json_string)
# current method to extract keys with values, but doesn’t pick up nested ones
serialized_json.map { |k,v|
puts k.titleize
puts v[1].present? ? v[1] : '<empty>'
}
# serialized_json (1st output)
{
"name" => [
[0] nil,
[1] "Epping Forest"
],
"job_address" => [
[0] nil,
[1] "51 Epping Forest, Essex, ES1 SAD"
]
}
# serialized_json (2nd output)
{
"quote" => [
[0] {
"quote" => {
"url" => nil
}
},
[1] {
"quote" => {
"url" => "/tmp/uploads/1433181671-22986-9554/file.pdf"
}
}
]
}
The values I’d like to extract from my method are:
"Name"
"Epping Forest"
"Quote Url"
"/tmp/uploads/1433181671-22986-9554/file.pdf"
In your below code, I assume you're putting [0] and [1] as index numbers to help the readers of your code, as it is not valid Ruby syntax.
"name" => [
[0] nil,
[1] "Epping Forest"
],
As for extracting 1 level deep keys and values, since it's a bit unclear to me how you would like your output formatted, I'ma ssuming a 1-D array is fine...in which case, something like this would give you the keys and values:
serialized_json hash w/valid Ruby syntax
{
"name" => [
nil,
"Epping Forest"
],
"job_address" => [
nil,
"51 Epping Forest, Essex, ES1 SAD"
]
}
Then:
serialized_json.map { |k, v| [k, v] }.flatten.reject { |x| x.nil? }
outputs:
["name", "Epping Forest", "job_address", "51 Epping Forest, Essex, ES1 SAD"]
I hope this helps :)
#str = ""
def hash_checks(hash)
hash.each do |k,v|
if v.is_a?(Array)
v.each_with_index do |element, index|
if element.is_a?(Hash)
#str = element[index] + #str if hash_check(element)
else
#str << k.titleize + "\n" + element + "\n" unless element.nil?
end
end
elsif v.is_a?(Hash)
#str << k.titleize if hash_check(v)
end
end
puts #str
end
def hash_check(hash)
add_tag = false
hash.each do |k,v|
if v.is_a?(Hash)
#str = k.titleize + " " + #str if hash_check(v)
else
#str << k.titleize + "\n" + v + "\n" unless v.nil?
add_tag = true
end
end
add_tag
end
I've excluded the '<empty>' value for nil because the data you would want to extract is the one that matters.
There's an inconsistency on the data you want though, since on your first serialized json, there are 2 non-nil values, but you just want one.
This method includes both, and runs even for multi-level nests :)

Storing duplicate keys in hash

I am expecting duplicate key to be stored with value in student_hash, but due to absence of multimap in ruby, it is not allowed. One solution is to capture all this information in separate two arrays based on selection(id/type/school_id), but input data might have so much depth, and array size cannot be determined. Can the same behavior (expected output) be achieved using a hash? Please suggest.
Expected output:
{:id=>"101", :type=>"junior", :school_id=>"IS1.4598"}
{:id=>"103", :type=>"Senior", :school_id=>"IS1.098"}
Code:
require 'json'
raw_data='{
"Enquiry": "get_all_student_info",
"success": true,
"payload": [
{
"standard_version": "1.4",
"country_id": "USA_01",
"parent": "USA_IS1",
"id": "101",
"company": "Govt",
"type": "Junior",
"subsystem_type": "Govtgirlsschool",
"school_id": "IS1.098"
},
{
"standard_version": "1.4",
"country_id": "NZ_01",
"parent": "NZ_IS1",
"id": "103",
"company": "Private",
"type": "Senior",
"subsystem_type": "Govtboysschool",
"school_id": "IS1.098"
}
],
"error": ""
}'
def student_hash(set, result = {})
if set.class == Hash
set.each do |k, v|
if("#{k}"=="id" || "#{k}"=="type" || "#{k}"=="school_id")
puts result ["#{k}".to_sym] = "#{v}"
end
if v.class == Hash
result = student_hash(v, result)
elsif v.class == Hash || v.class == Array
result = student_hash(v, result)
end
end
elsif set.class == Array
set.each do |a|
result = student_hash(a, result)
end
end
result
end
student_hash(JSON.parse(raw_data))
# => {:id=>"103", :type=>"Senior", :school_id=>"IS1.098"}
Not to much clear but, This is enough to expected output for your raw_data
data = JSON.parse(raw_data)
data["payload"].map {|i| { :id => i["id"], type: i["type"], school_id: i["school_id"] }}
Now you can print hoe ever you want.
Try with below function, it will give result what you are expecting.
def content_display( result = [] )
student_array = []
#array = []
result["payload"].map {|i|
student_array={ :id => i["id"], :type => i["type"], :school_id => i["subsystem_instance_id"] }
#array << student_array
}
#array
end
array=content_display(JSON.parse(raw_data))
puts array
I think this may help you.
all_data = []
row = {:id=>"101", :type=>"junior", :school_id=>"IS1.4598"}
all_data << row
For example
students = Student.all
all_student = []
students.each do |student|
student_hash = {:id=>student.id, :type=>student.type, :school_id=>student.school_id}
all_student << student_hash
end
puts all_student
You will get result something like that
[{:id=>"101", :type=>"junior", :school_id=>"IS1.4598"}
{:id=>"103", :type=>"Senior", :school_id=>"IS1.098"}]

Resources