Hello I have a question similar to this (Add a key value pair to all hashes in an array of hashes) but with a bit more complexity
def location_records(options)
materials = [
{
name: 'object1'
area: 'USA'
},
{
name: 'object2'
area: 'USA'
}
]
materials.map!{ |material| record_additional_info(material, options) if %w[USA].include?(options.extra_fields_required) }
end
def record_additional_info(materials, options)
materials[:zip] = 63123
materials[:state] = 'Missouri'
materials[:city] = 'Kansas City'
end
Say I have this - and I have an array of hash
[{name: 'object1', area: 'USA'}, {name: 'object2', area: 'USA'}]
I want to selective only add additional keys to the hash ONLY if the extra_fields_required is contained in the list of String I provided, say only USA.
So the output of this should be
[{name: 'object1', area: 'USA', zip: 63123, state: 'Missouri', city: 'Kansas City'}, {name: 'object2', area: 'USA', zip: 63123, state: 'Missouri', city: 'Kansas City'}]
But for some reason, I keep getting -
NoMethodError: undefined method `each' for "Kansas City":String
You should return the material hash from an invoked method
def location_records(options)
materials = [
{
name: 'object1',
area: 'USA'
},
{
name: 'object2',
area: 'USA'
}
]
materials.map!{ |material| record_additional_info(material) if %w[USA].include?(options.extra_fields_required) }
puts materials # Prints modified array of hashes
end
def record_additional_info(material)
material[:zip] = 63123
material[:state] = 'Missouri'
material[:city] = 'Kansas City'
material # Return the hash object
end
Output:
{:name=>"object1", :area=>"USA", :zip=>63123, :state=>"Missouri", :city=>"Kansas City"}
{:name=>"object2", :area=>"USA", :zip=>63123, :state=>"Missouri", :city=>"Kansas City"}
Related
I want to merge the ids of same name values
hashes = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
Expected:
[{
id: [3456824,987456],
name: 'John'
},{
id: 6578954,
name: 'Vicky'
}]
how I can achieve this in ruby on rails?
Here is a one liner:
hashes = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
result = hashes.group_by{|h| h[:name] }.map{|k, v| {id: v.map{|x| x[:id]}, name: k}}
puts result
Check this repl: https://repl.it/repls/ShadowyCornyVideogames
Here are two ways to compute the desired result.
Use the form of Hash::new that takes a block
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>[6578954], :name=>"Vicky"}]
The first step of this calculation1 is
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end
#=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}
If a hash is defined
h = Hash.new { |h,k| h[k] = [] }
and (possibly after having added key-value pairs) h has no key k, h[k] in
h[k] << v
causes the block { |h,k| h[k] = [] } to be executed, resulting in the key value pair k=>[] being added to h, then << v is executed, changing h[k] from [] to [k].
Notice that this returns :id=>[6578954], rather than :id=>6578954, which was asked for by the question. Having all values of :id return an array avoids the need to check if :id returns an array or integer in subsequent code that processes the return value of this operation.
If :id=>6578954, were desired, one could write
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end.transform_values { |v| v.size==1 ? v.first : v }.
map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>6578954, :name=>"Vicky"}]
See Hash#transform_values.
Use the form of Hash#update (a.k.a. merge!) that employs a block to determine the values of keys that are present in both hashes being merged
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>[g[:id]]) { |_,o,n| o+n }
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>[6578954], :name=>"Vicky"}]
If :id=>6578954, rather than :id=>[6578954], were desired:
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>g[:id]) { |_,o,n| [*o,n] }
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>6578954, :name=>"Vicky"}]
Notice that here update's argument is g[:name]=>g[:id] whereas it was previously g[:name]=>[g[:id]].
The first step is as follows.
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>g[:id]) { |_,o,n| [*o,n] }
end
#=> {"John"=>[3456824, 987456], "Vicky"=>6578954}
In general, one or both of these approaches can be taken when Enumerable#group_by can be used. The reverse if often true also. The choice among these methods is a matter of personal taste.
1. A variant of the first part of this calculation is hashes.each_with_object({}) { |g,h| (h[g[:name]] ||= []) << g[:id] } #=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}.
You can do it like this:
args = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
value_pairs = args.map { |h| h.values_at(:name, :id) }
grouped_by_name = value_pairs.group_by(&:first).transform_values { |arr| arr.map(&:last) }
as_hashes = grouped_by_name.map { |name, ids| { id: ids, name: name } }
One more possible solution is:
array = [
{
id: 3456824,
name: "John"
},
{
id: 6578954,
name: "Vicky"
},
{
id: 987456,
name: "John"
}
]
grouped_by_name = array.each_with_object(Hash.new {|h,k| h[k] = [] }) do |hash, result|
result[hash[:name]] << hash[:id]
end
=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}
grouped_by_name.map do |grouped_hash|
{
id: grouped_hash.last,
name: grouped_hash.first
}
end
=> [{:id=>[3456824, 987456], :name=>"John"}, {:id=>[6578954], :name=>"Vicky"}]
I'm trying to iterate over hash and return new hash. My original hash is:
companies = {
company_id: {
"0": { title: "Google", address: "New str" },
"1": { title: "Facebook", address: "Old str." },
"2": { title: "Amazon", address: "River str." }
}
}
I want to return hash that is structured this way:
{
title: "Google",
address: "New str."
}
If company_id equal to "0" I would need to return details of that company, similar to below:
companies.each do |k,v|
v.each do |k,v|
if k.to_s == "0"
title: v[:title]
address: v[:address]
end
end
end
Iteration above doesn't return me hash, how can I get structured hash that I need? Thanks.
Simply do
companies[:company_id][:"0"]
# { title: "Google", address: "New str." }
I want to create a nested hash using four values type, name, year, value. ie, key of the first hash will be type, value will be another hash with key name, then value of that one will be another hash with key year and value as value.
The array of objects I'm iterating looks like this:
elements = [
{
year: '2018',
items: [
{
name: 'name1',
value: 'value1',
type: 'type1',
},
{
name: 'name2',
value: 'value2',
type: 'type2',
},
]
},
{
year: '2019',
items: [
{
name: 'name3',
value: 'value3',
type: 'type2',
},
{
name: 'name4',
value: 'value4',
type: 'type1',
},
]
}
]
And I'm getting all values together using two loops like this:
elements.each do |element|
year = element.year
element.items.each |item|
name = item.name
value = item.value
type = item.type
# TODO: create nested hash
end
end
Expected output is like this:
{
"type1" => {
"name1" => {
"2018" => "value1"
},
"name4" => {
"2019" => "value4"
}
},
"type2" => {
"name2" => {
"2018" => "value2"
},
"name3" => {
"2019" => "value3"
}
}
}
I tried out some methods but it doesn't seems to work out as expected. How can I do this?
elements.each_with_object({}) { |g,h| g[:items].each { |f|
h.update(f[:type]=>{ f[:name]=>{ g[:year]=>f[:value] } }) { |_,o,n| o.merge(n) } } }
#=> {"type1"=>{"name1"=>{"2018"=>"value1"}, "name4"=>{"2019"=>"value4"}},
# "type2"=>{"name2"=>{"2018"=>"value2"}, "name3"=>{"2019"=>"value3"}}}
This uses the form of Hash#update (aka merge!) that employs a block (here { |_,o,n| o.merge(n) } to determine the values of keys that are present in both hashes being merged. See the doc for definitions of the three block variables (here _, o and n). Note that in performing o.merge(n) o and n will have no common keys, so a block is not needed for that operation.
Assuming you want to preserve the references (unlike in your desired output,) here you go:
elements = [
{
year: '2018',
items: [
{name: 'name1', value: 'value1', type: 'type1'},
{name: 'name2', value: 'value2', type: 'type2'}
]
},
{
year: '2019',
items: [
{name: 'name3', value: 'value3', type: 'type2'},
{name: 'name4', value: 'value4', type: 'type1'}
]
}
]
Just iterate over everything and reduce into the hash. On the structures of known shape is’s a trivial task:
elements.each_with_object(
Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) } # for deep bury
) do |h, acc|
h[:items].each do |item|
acc[item[:type]][item[:name]][h[:year]] = item[:value]
end
end
#⇒ {"type1"=>{"name1"=>{"2018"=>"value1"},
# "name4"=>{"2019"=>"value4"}},
# "type2"=>{"name2"=>{"2018"=>"value2"},
# "name3"=>{"2019"=>"value3"}}}
I'm trying to merge hashes if a specific key has the same value.
here is the array
[{
id: 77,
member_phone: "9876543210",
created_at: "2017-05-03T11:06:03.000Z",
name: "Sure"
},
{
id: 77,
member_phone: "123456789",
created_at: "2017-05-03T11:06:03.000Z",
name: "Sure"
},
{
id: 78,
member_phone: "12345",
created_at: "2017-05-03T11:06:03.000Z",
name: "XYZ"
}]
and the required output:
[{
id: 77,
member_phone: "123456789,9876543210",
created_at: "2017-05-03T11:06:03.000Z",
name: "Sure"
},
{
id: 78,
member_phone: "12345",
created_at: "2017-05-03T11:06:03.000Z",
name: "XYZ"
}]
here's the code I tried:
merge_users.group_by { |h1| h1["id"] }.map do |k,v|
{ "id" => k, :member_phone => v.map { |h2| h2[:member_phone] }.join(", ") }
end
how can I do it?
The following code would work for your given example.
code
result = arr.group_by {|h| h[:id]}.values.map do |arr|
arr.reduce do |h1, h2|
h1.merge(h2) do |k, ov, nv|
ov.eql?(nv) ? ov : [ov, nv].join(",")
end
end
end
p result
#=>[{:id=>77, :member_phone=>"9876543210,123456789", :created_at=>"2017-05-03T11:06:03.000Z", :name=>"Sure"}, {:id=>78, :member_phone=>"12345", :created_at=>"2017-05-03T11:06:03.000Z", :name=>"XYZ"}]
How about:
grouped = data.group_by do |item|
item[:id]
end
combined = grouped.map do |_id, hashes|
hashes.inject({}) do |memo, hash|
memo.merge(hash)
end
end
It works in two passes:
First group all hashes by the value of the :id key
This returns a Hash with the id as key, and an array (of all the hashes with this id) as value.
In a second pass all the hashes are merged and mapped to an array again.
arr = [
{ id: 77, phone: "9876543210", name: "Sure" },
{ id: 77, phone: "123456789", name: "Sure" },
{ id: 78, phone: "12345", name: "XYZ" }
]
You could use the form of Hash#update (aka merge!) that uses a block to compute the values of keys that are present in both hashes being merged.
arr.each_with_object({}) { |g,h| h.update(g[:id]=>g) { |_,o,n|
o.merge(phone: "#{o[:phone]}#{n[:phone]}") } }.values
#=> [{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
# {:id=>78, :phone=>"12345", :name=>"XYZ"}]
Note that the receiver of Hash#values is the following.
#=> {77=>{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
# 78=>{:id=>78, :phone=>"12345", :name=>"XYZ"}}
See the doc for Hash#update for definitions of the three block variables _, o and n. I used an underscore for the first variable (a valid name for a local variable) to signify that it is not used in the block calculation (a common practice).
Note that Hash#update can almost always be used when Enumerable#group_by can be used, and vice-versa.
Here's one way to use Hash#group_by here.
arr.group_by { |h| h[:id] }.
map { |_,a| a.first.merge(phone: a.map { |h| h[:phone] }.join) }
#=> [{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
# {:id=>78, :phone=>"12345", :name=>"XYZ"}]
Note that
arr.group_by { |h| h[:id] }
#=> {77=>[{:id=>77, :phone=>"9876543210", :name=>"Sure"},
# {:id=>77, :phone=>"123456789", :name=>"Sure"}],
# 78=>[{:id=>78, :phone=>"12345", :name=>"XYZ"}]}
So I'm using the yelp API, and after I make a GET request I get back a response of businesses. In order to work with that response I'm using .map
Example:
mappedResults = yelpSearch.businesses.map {|l| {id: l.id, name: l.name, categories:l.categories, rating: l.rating, review_count: l.review_count, url: l.url, phone: l.phone}}
My problem is that sometimes l.phone is not returned for some records in the response, and I get the error:
undefined method `phone' for #<BurstStruct::Burst:0x007fba47c7a228>
My question is how do I refactor this code so that if a record doesn't have phone it will either leave it null (or worst cast empty string)
Any help is appreciated
JSON structure is as such for each business in the response
{
region: {
span: {
latitude_delta: 0,
longitude_delta: 0
},
center: {
latitude: 38.054117,
longitude: -84.439002
}
},
total: 23,
businesses: [
{
is_claimed: false,
rating: 5,
mobile_url: "http://m.yelp.com/biz/vineyard-community-church-lexington",
rating_img_url: "http://s3-media1.ak.yelpcdn.com/assets/2/www/img/f1def11e4e79/ico/stars/v1/stars_5.png",
review_count: 2,
name: "Vineyard Community Church",
snippet_image_url: "http://s3-media4.ak.yelpcdn.com/photo/VoeMtbk7NRFi6diksSUtOQ/ms.jpg",
rating_img_url_small: "http://s3-media1.ak.yelpcdn.com/assets/2/www/img/c7623205d5cd/ico/stars/v1/stars_small_5.png",
url: "http://www.yelp.com/biz/vineyard-community-church-lexington",
phone: "8592582300",
snippet_text: "I have been a member of Vineyard Community Church since 2004. Here you will find a modern worship service with a full band, witty speakers who teach...",
image_url: "http://s3-media3.ak.yelpcdn.com/bphoto/D71eikniuaHjdOC8DB6ziA/ms.jpg",
categories: [
[
"Churches",
"churches"
]
],
display_phone: "+1-859-258-2300",
rating_img_url_large: "http://s3-media3.ak.yelpcdn.com/assets/2/www/img/22affc4e6c38/ico/stars/v1/stars_large_5.png",
id: "vineyard-community-church-lexington",
is_closed: false,
location: {
city: "Lexington",
display_address: [
"1881 Eastland Pwky",
"Lexington, KY 40505"
],
geo_accuracy: 8,
postal_code: "40505",
country_code: "US",
address: [
"1881 Eastland Pwky"
],
coordinate: {
latitude: 38.054117,
longitude: -84.439002
},
state_code: "KY"
}
}
]
}
If you're using Rails 4.0 or newer, the #presence method is really helpful for this. You would use it like this:
mappedResults = yelpSearch.businesses.map {|l| {id: l.id.presence, #... etc
or like this
mappedResults = yelpSearch.businesses.map {|l| {id: l.id.presence || "default id", # ...
Update
Reading your code again, #presence might not work in this case, since the method isn't defined. Here's a longer (uglier) snippet that should work:
mappedResults = yelpSearch.businesses.map do |l|
id: l.respond_to(:id) ? l.id : "default id",
# ... other properties
end
Update from OP
This worked - thank you! Note I had to tweak syntax a bit to respond_to?('method_name')
mappedResults = yelpSearch.businesses.map {|l|
{
name: l.respond_to?("name") ? l.name : "nameless",
rating: l.respond_to?("rating") ? l.rating : "unrated",
# ... other properties
}}