I have a response from api as a parsed JSON which is an array of hashes, I need to create a new hash with custom keys and the values that I will take from that api response
#array of hashes looks like this:
[{:id=>1,
:name=>"Leanne Graham",
:username=>"Bret",
:email=>"Sincere#april.biz",
:address=>
{:street=>"Kulas Light",
:suite=>"Apt. 556",
:city=>"Gwenborough",
:zipcode=>"92998-3874",
:geo=>{:lat=>"-37.3159", :lng=>"81.1496"}},
:phone=>"1-770-736-8031 x56442",
:website=>"hildegard.org",
:company=>
{:name=>"Romaguera-Crona", :catchPhrase=>"Multi-layered client-server neural-net", :bs=>"harness real-time e-markets"}}]
(and there are 4 more people). I only need 2 keys and the new hash should look something like this
ideal_hash = {
:full_name => ["Leanne Graham", "another name", "another name", "etc"]
:email => ["Sincere#april.biz", "some email", "another one", "etc"]
}
there are gonna be more values in array but just these two custom keys.
I tried taking values from hash and zipping it with an array of keys but the problem is that I only get 2 values instead of 4 because there are only 2 keys, I tried to map but it didnt quite work either. please help
I only need 2 keys .. :full_name and :email
input.each_with_object({full_name: [], email: []}) do |e, a|
a[:full_name] << e[:name]
a[:email] << e[:email]
end
Related
Hash to csv
hash :
{
"employee" => [
{
"name" => "Claude",
"lastname"=> "David",
"profile" => [
"age" => "43",
"jobs" => [
{
"name" => "Ingeneer",
"year" => "5"
}
],
"graduate" => [
{
"place" => "Oxford",
"year" => "1990"
},
],
"kids" => [
{
"name" => "Viktor",
"age" => "18",
}
]
}
}]
this is an example of an hash I would work on. So, as you can see, there is many level of array in it.
My question is, how do I put it properly in a CSV file?
I tried this :
column_names = hash['employee'].first.keys
s=CSV.generate do |csv|
csv << column_names
hash['scrap'].each do |x|
csv << x.values
end
end
File.write('myCSV.csv', s)
but I only get name, lastname and profile as keys, when I would catch all of them (age, jobs, name , year, graduate, place...).
Beside, how can I associate one value per case?
Because I actually have all employee[x] which take a cell alone. Is there any parameters I have missed?
Ps: This could be the following of this post
A valid CSV output has a fixed number of columns, your hash has a variable number of values. The keys jobs, graduate and kids could all have multiple values.
If your only goal is to make a CSV output that can be read in Excel for example, you could enumerate your Hash, take the maximum number of key/value pairs per key, total it and then write your CSV output, filling the blank values with "".
There are plenty of examples here on Stack Overflow, search for "deep hash" to start with.
Your result would have a different number of columns with each Hash you provide it.
That's too much work if you ask me.
If you just want to present a readable result, your best and easiest option is to convert the Hash to YAML which is created for readability:
require 'yaml'
hash = {.....}
puts hash.to_yaml
employee:
- name: Claude
lastname: David
profile:
- age: '43'
jobs:
- name: Ingeneer
year: '5'
graduate:
- place: Oxford
year: '1990'
kids:
- name: Viktor
age: '18'
If you want to convert the hash to a CSV file or record, you'll need to get a 'flat' representation of your keys and values. Something like the following:
h = {
a: 1,
b: {
c: 3,
d: 4,
e: {
f: 5
},
g: 6
}
}
def flat_keys(h)
h.keys.reject{|k| h[k].is_a?(Hash)} + h.values.select{|v| v.is_a?(Hash)}.flat_map{|v| flat_keys(v)}
end
flat_keys(h)
# [:a, :c, :d, :g, :f]
def flat_values(h)
h.values.flat_map{|v| v.is_a?(Hash) ? flat_values(v) : v}
end
flat_values(h)
# [1, 3, 4, 5, 6]
Then you can apply that to create a CSV output.
It depends on how those fields are represented in the database.
For example, your jobs has a hash with name key and your kids also has a hash with name key, so you can't just 'flatten' them, because keys have to be unique.
jobs is probably another model (database table), so you probably would have to (depending on the database) write it separately, including things like the id of the related object and so on.
Are you sure you're not in over your head? Judging from your last question and because you seem to treat csv's as simple key-values pair omitting all the database representation and relations.
I have a set of data that is an array of hashes, with each hash representing one record of data:
data = [
{
:id => "12345",
:bucket_1_rank => "2",
:bucket_1_count => "12",
:bucket_2_rank => "7",
:bucket_2_count => "25"
},
{
:id => "45678",
:bucket_1_rank => "2",
:bucket_1_count => "15",
:bucket_2_rank => "9",
:bucket_2_count => "68"
},
{
:id => "78901",
:bucket_1_rank => "5",
:bucket_1_count => "36"
}
]
The ranks values are always between 1 and 10.
What I am trying to do is select each of the possible values for the rank fields (the :bucket_1_rank and :bucket_2_rank fields) as keys in my final resultset, and the values for each key will be an array of all the values in its associated :bucket_count field. So, for the data above, the final resulting structure I have in mind is something like:
bucket 1:
{"2" => ["12", "15"], "5" => ["36"]}
bucket 2:
{"7" => ["25"], "9" => ["68"]}
I can do this working under the assumption that the field names stay the same, or through hard coding the field/key names, or just using group_by for the fields I need, but my problem is that I work with a different data set each month where the rank fields are named slightly differently depending on the project specs, and I want to identify the names for the count and rank fields dynamically as opposed to hard coding the field names.
I wrote two quick helpers get_ranks and get_buckets that use regex to return an array of fieldnames that are either ranks or count fields, since these fields will always have the literal string "_rank" or "_count" in their names:
ranks = get_ranks
counts = get_counts
results = Hash.new{|h,k| h[k] = []}
data.each do |i|
ranks.each do |r|
unless i[r].nil?
counts.each do |c|
results[i[r]] << i[c]
end
end
end
end
p results
This seems to be close, but feels awkward, and it seems to me there has to be a better way to iterate through this data set. Since I haven't worked on this project using Ruby I'd use this as an opportunity to improve my understanding iterating through arrays of hashes, populating a hash with arrays as values, etc. Any resources/suggestions would be much appreciated.
You could shorten it to:
result = Hash.new{|h,k| h[k] = Hash.new{|h2,k2| h2[k2] = []}}
data.each do |hsh|
hsh.each do |key, value|
result[$1][value] << hsh["#{$1}_count".to_sym] if key =~ /(.*)_rank$/
end
end
puts result
#=> {"bucket_1"=>{"2"=>["12", "15"], "5"=>["36"]}, "bucket_2"=>{"7"=>["25"], "9"=>["68"]}}
Though this is assuming that :bucket_2_item_count is actually supposed to be :bucket_2_count.
I have two hashes, one looks like this:
{:id => "SG_5viWPcG0SLvszXbBxogLkT_51.514568_-0.126244#1300740367",
:name => "Shellys Shoes",
:lat => 51.5145683289,
:lng => -0.1262439936}
This is just one record, there are about 80,
The second hash I have is:
{"id":"SG_2zNWLdG9147g2ROvNWpDHr_51.512360_0.124480#1300740823",
"lat":51.5123596191,
"lng":-0.1244800016}
The hash above is again just one record, however it is a product of the HASH above after going through an API that does not return all the records, only the valid ones, what I want to do is compare the top hash with the bottom one and delete any records that are not present in the bottom hash,
for example if id:SG_5viWPcG0SLvszXbBxogLkT_51.514568_-0.126244#1300740367 is not in the the second hash then delete that record,
I can compare the hashes, but cant see how to delete if ID is not present?
Thanks guys!
edit:
these are the returned values....
{"points":[{"id":"SG_75oKOgvgFPLjwmdyAKA2rq_51.512825_-0.124655#1300740283","lat":51.5128250122,"lng":-0.1246550009},{"id":"SG_0Sz9CBF5t70tdAffTKYNSg_51.512360_-0.124388#1300740807","lat":51.5123596191,"lng":-0.1243880019},{"id":"SG_2zNWLdG9147g2ROvNWpDHr_51.512360_-0.124480#1300740823","lat":51.5123596191,"lng":-0.1244800016},{"id":"SG_5PvBx89sLPgplapegVJDFv_51.513100_-0.124809#1300740049","lat":51.5130996704,"lng":-0.1248089969},{"id":"SG_4luyHFi5R2f1w3cpjT61ik_51.513393_-0.124556#1300740719","lat":51.5133934021,"lng":-0.1245559976},{"id":"SG_4luyHFi5R2f1w3cpjT61ik_51.513393_-0.124556#1300740719","lat":51.5133934021,"lng":-0.1245559976},{"id":"SG_0pEvrpt7bs42jPAxFSrquC_51.512264_-0.124413#1300740807","lat":51.5122642517,"lng":-0.1244129986},]}
This is the original format:
[ { :id => "SG_2Km6LX3tEcFwx24eotTHIY_51.513016_-0.123721#1300740411",
:name => "French Connection Group Plc",
:lat => 51.5130157471,
:lng => -0.1237210035
}]
You can collect a list of the valid IDs from the returned values with something like:
valid_ids = returned["points"].collect { |point| point["id"] }
You can then remove the invalid values from the original with something like:
original.delete_if { |entry| !valid_ids.include? entry[:id] }
I've got an array of hashes representing objects as a response to an API call. I need to pull data from some of the hashes, and one particular key serves as an id for the hash object. I would like to convert the array into a hash with the keys as the ids, and the values as the original hash with that id.
Here's what I'm talking about:
api_response = [
{ :id => 1, :foo => 'bar' },
{ :id => 2, :foo => 'another bar' },
# ..
]
ideal_response = {
1 => { :id => 1, :foo => 'bar' },
2 => { :id => 2, :foo => 'another bar' },
# ..
}
There are two ways I could think of doing this.
Map the data to the ideal_response (below)
Use api_response.find { |x| x[:id] == i } for each record I need to access.
A method I'm unaware of, possibly involving a way of using map to build a hash, natively.
My method of mapping:
keys = data.map { |x| x[:id] }
mapped = Hash[*keys.zip(data).flatten]
I can't help but feel like there is a more performant, tidier way of doing this. Option 2 is very performant when there are a very minimal number of records that need to be accessed. Mapping excels here, but it starts to break down when there are a lot of records in the response. Thankfully, I don't expect there to be more than 50-100 records, so mapping is sufficient.
Is there a smarter, tidier, or more performant way of doing this in Ruby?
Ruby <= 2.0
> Hash[api_response.map { |r| [r[:id], r] }]
#=> {1=>{:id=>1, :foo=>"bar"}, 2=>{:id=>2, :foo=>"another bar"}}
However, Hash::[] is pretty ugly and breaks the usual left-to-right OOP flow. That's why Facets proposed Enumerable#mash:
> require 'facets'
> api_response.mash { |r| [r[:id], r] }
#=> {1=>{:id=>1, :foo=>"bar"}, 2=>{:id=>2, :foo=>"another bar"}}
This basic abstraction (convert enumerables to hashes) was asked to be included in Ruby long ago, alas, without luck.
Note that your use case is covered by Active Support: Enumerable#index_by
Ruby >= 2.1
[UPDATE] Still no love for Enumerable#mash, but now we have Array#to_h. It creates an intermediate array, but it's better than nothing:
> object = api_response.map { |r| [r[:id], r] }.to_h
Something like:
ideal_response = api_response.group_by{|i| i[:id]}
#=> {1=>[{:id=>1, :foo=>"bar"}], 2=>[{:id=>2, :foo=>"another bar"}]}
It uses Enumerable's group_by, which works on collections, returning matches for whatever key value you want. Because it expects to find multiple occurrences of matching key-value hits it appends them to arrays, so you end up with a hash of arrays of hashes. You could peel back the internal arrays if you wanted but could run a risk of overwriting content if two of your hash IDs collided. group_by avoids that with the inner array.
Accessing a particular element is easy:
ideal_response[1][0] #=> {:id=>1, :foo=>"bar"}
ideal_response[1][0][:foo] #=> "bar"
The way you show at the end of the question is another valid way of doing it. Both are reasonably fast and elegant.
For this I'd probably just go:
ideal_response = api_response.each_with_object(Hash.new) { |o, h| h[o[:id]] = o }
Not super pretty with the multiple brackets in the block but it does the trick with just a single iteration of the api_response.
For the sake of convenience I am trying to assign multiple values to a hash key in Ruby. Here's the code so far
myhash = { :name => ["Tom" , "Dick" , "Harry"] }
Looping through the hash gives a concatenated string of the 3 values
Output:
name : TomDickHarry
Required Output:
:name => "Tom" , :name => "Dick" , :name => "Harry"
What code must I write to get the required output?
myhash.each_pair {|k,v| v.each {|n| puts "#{k} => #{n}"}}
#name => Tom
#name => Dick
#name => Harry
The output format is not exactly what you need, but I think you get the idea.
The answers from Rohith and pierr are fine in this case. However, if this is something you're going to make extensive use of it's worth knowing that the data structure which behaves like a Hash but allows multiple values for a key is usually referred to as a multimap. There are a couple of implementations of this for Ruby including this one.
You've created a hash with the symbol name as the key and an array with three elements as the value, so you'll need to iterate through myhash[:name] to get the individual array elements.
re: the issue of iterating over selective keys. Try using reject with the condition inverted instead of using select.
e.g. given:
{:name=>["Tom", "Dick", "Harry"], :keep=>[4, 5, 6], :discard=>[1, 2, 3]}
where we want :name and :keep but not :discard
with select:
myhash.select { |k, v| [:name, :keep].include?(k) }
=> [[:name, ["Tom", "Dick", "Harry"]], [:keep, [4, 5, 6]]]
The result is a list of pairs.
but with reject:
myhash.reject { |k, v| ![:name, :keep].include?(k) }
=> {:name=>["Tom", "Dick", "Harry"], :keep=>[4, 5, 6]}
The result is a Hash with only the entries you want.
This can then be combined with pierr's answer:
hash_to_use = myhash.reject { |k, v| ![:name, :keep].include?(k) }
hash_to_use.each_pair {|k,v| v.each {|n| puts "#{k} => #{n}"}}