Creating nested hash - Ruby - ruby

I have two unique hashes and I want to write a code to create a single nested hash from those two. I understand one can create a nested hash manually, but I'd rather be able to write code to do that.
cats = {"name" => "alpha", "weight" => "10 pounds"}
dogs = ("name" => "beta", "weight"=>"20 pounds"}
Ideally my nested hash would resemble:
pets = {"dogs"=>{"name"=>"beta", "weight"=>"20 pounds"}, "cats"=>{"name"=>"alpha", "weight"=>"10
pounds"}}
I'd really appreciate it if someone could break down the code to do the above for me.
Thank you!!

You can do it easily like this.
pets = {"dogs"=>dogs, "cats"=>cats}
Output:
{"dogs"=>{"name"=>"beta", "weight"=>"20 pounds"}, "cats"=>{"name"=>"alpha", "weight"=>"10 pounds"}}

Just for fun, you can also get all the local variables automatically and save them into a hash:
cats = {"name" => "alpha", "weight" => "10 pounds"}
dogs = {"name" => "beta", "weight"=>"20 pounds"}
puts binding.local_variables.map{|var| [var.to_s, binding.local_variable_get(var)] }.to_h
# {"cats"=>{"name"=>"alpha", "weight"=>"10 pounds"}, "dogs"=>{"name"=>"beta", "weight"=>"20 pounds"}}
But please the other answer instead.

Related

How to get the right csv format from hash in ruby

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.

Ruby - Array of Hashes, Trying to Select Multiple Keys and Group By Key Value

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.

Sorting values from a Ruby hash, ignoring case

This is how I'm handling values in a Ruby hash to get an alpha-numeric lower-case sorted output (extreme example):
myhash = {
"x" => "zebra",
"one" => "1",
"alpeh" => "alpha",
"lower" => "january",
"1" => "January",
"2" => "February",
"answer" => "42"
}
m = myhash.values
puts m.map{|i| i.downcase}.sort
Output:
1
42
alpha
february
january
january
zebra
This works fine and I don't have a problem with it, but want to know if there's there a simpler/more efficient way I'm missing?
Since you want to modify the values (outputting lowercase strings), I don't think you can do anything better.
If outputting the original values is OK as long as they are well sorted, you could use this :
myhash.values.sort_by{|h|h.downcase}
Edit : Thanks to Casper, here's a more compact version :
myhash.values.sort_by(&:downcase)
Edit : Thanks to Mischa, if you want to keep the output you provided :
myhash.values.map(&:downcase).sort
More efficient way than using hash.values (since it creates a temperary array and may be time/space consuming if the hash is large)
myhash.sort_by{|_,v| v.downcase}
This might be more efficient...
m.sort {|a, b| a.casecmp(b)}
=> ["1", "42", "alpha", "February", "January", "january", "zebra"]
Just use a test function that ignores the case on the inputs. It depends on whether you want you output array to have duplicate values (e.g. january/january vs January/january).

Ruby get object keys as array

I am new to Ruby, if I have an object like this
{"apple" => "fruit", "carrot" => "vegetable"}
How can I return an array of just the keys?
["apple", "carrot"]
hash = {"apple" => "fruit", "carrot" => "vegetable"}
array = hash.keys #=> ["apple", "carrot"]
it's that simple
An alternative way if you need something more (besides using the keys method):
hash = {"apple" => "fruit", "carrot" => "vegetable"}
array = hash.collect {|key,value| key }
obviously you would only do that if you want to manipulate the array while retrieving it..
Like taro said, keys returns the array of keys of your Hash:
http://ruby-doc.org/core-1.9.3/Hash.html#method-i-keys
You'll find all the different methods available for each class.
If you don't know what you're dealing with:
puts my_unknown_variable.class.to_s
This will output the class name.
Use the keys method: {"apple" => "fruit", "carrot" => "vegetable"}.keys == ["apple", "carrot"]

Help with transforming arrays and hashes

I'm trying to use Highcharts in my web app, but I'm having some trouble getting my arrays to match the output that highcharts needs, essentially hightcharts needs output like this in it's JS:
series: [{
name: 'person',
data: [1562, 873, 1457]
}, {
name: 'car',
data: [7323, 324, 1233]
}, {
name: 'SUV',
data: [832, 6232, 4432]
}]
Where each item in the data array in this case is a new day, my problem is that my data is organized a bit differently, and trying to transform my data to match this has been a headache. basically my output is like so:
[
{:date=>"Sun, 10 Apr 2011", :object_types=>[
{:count=>4279, :class_name=>"person"},
{:count=>8785, :class_name=>"car"},
{:count=>2153, :class_name=>"SUV"}
]},
{:date=>"Sat, 09 Apr 2011", :object_types=>[
{:count=>12206, :class_name=>"person"},
{:count=>29095, :class_name=>"car"},
{:count=>7565, :class_name=>"SUV"}
]},
{:date=>"Fri, 08 Apr 2011", :object_types=>[
{:count=>4159, :class_name=>"person"},
{:count=>8043, :class_name=>"car"},
{:count=>1982, :class_name=>"SUV"}
]}
]
So it seems that I need to primarily have the items first grouped by (in my case) :class_name, then within that the :count by day as each item in data.
I'm having trouble find the the "ruby way" to do this elegantly, I'm not super awesome at manipulating arrays and hashes as you can probably see, but any bit of help in guiding me towards accomplishing this the right way is appreciated.
Secondly, I tried to accomplish this using group() in my query originally but that doesnt seem to be helping much, thoughts? Another thing I'll have to solve is filling dates with "0" if there are no records for it... but I'll tackle that next I think.
I would use Enumerable#group_by, because that's the harder part; the rest is just basic transformation:
r = data.flat_map{|h| h[:object_types]}.
group_by{|h| h[:class_name]}.
map do |class_name, hashes|
{:name => class_name, :data => hashes.map{|h| h[:count]}}
end
Note: group_by is in ActiveRecord or Ruby 1.8.7+. flat_map is new to Ruby 1.9.2. Use the right version, or require "backports".
Something like this should work:
def convert_data(input)
hash = {}
input.each do |o|
o[:object_types].each do |ot|
if hash[ot[:class_name]]
hash[ot[:class_name]] << ot[:count]
else
hash[ot[:class_name]] = [ot[:count]]
end
end
end
hash.map {|k,v| {'name' => k, 'data' => v}}
end
You can call this function with your input data and you've got an object which fits the format you expect for the Highcharts library.
aux = Hash.new(Array.new)
series = Array.new
# After this code the aux hash will look something like this: {:person => [4159, 1231, 255], :suv => [1231, 4123, 5411], :car => [321, 312, 541]}
raw_array.each do |date_hash|
date_hash[:object_types].each do |inf|
aux[inf[:class_name]] << inf[:count]
end
end
aux.each { |k,v| series << {:name => k.to_s, :data => v} }
Then that series array is what you need. Just render it like json.

Resources