Help with transforming arrays and hashes - ruby

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.

Related

Isolating and displaying a specific element within a hash

I am currently having trouble writing a test that addresses the eligibility_settings of a record I have. I am having trouble pulling out one of the specific elements from this hash.
Specifically I want to test that by making a change elsewhere in a different function that changes the min age of a specific player, and so what I am really trying to test is the eligibility_settings.min_age. But i'm having trouble within my test isolating that out.
My hash looks like this
{
:name => "player1",
:label => "redTeam_1_1",
:group => "adult",
:teamId => 7,
:eligibility_settings => {
"min_age" => 18,
"player_gender" => "female",
"union_players_only" => true
}
}
However when I try looping through this hash, I am having trouble isolating that one element.
i've tried something like
team.get_players.first.map do |settings, value|
value.tap do |x, y|
y[3]
end
end
However It seems like what i've been trying, and my approach has not been working quite right.
Would anyone have any idea what I could do with this?
Although #SergioTulentsev gave the proper response, in the future if you are going to be looping through hashes, below is one way to iterate through the keys and grab the value you want.
hash = {
:name => "player1",
:label => "redTeam_1_1",
:group => "adult",
:teamId => 7,
:eligibility_settings => {
"min_age" => 18,
"player_gender" => "female",
"union_players_only" => true
}
}
hash.map do |settings, value|
p hash[:eligibility_settings]['min_age'] if settings == :eligibility_settings
end # output 18

Consolidate csv data in ruby to get totals/sums of unique values

I'm still struggling with a basic problem I have not found an answer to online.
I am getting CSV like data as name and quantity:
Foo, 1.5
Bar, 1.2
Foo, 1.1
...
And want to consolidate it to unique names with the totals as a new value:
Foo, 2.6 #total of both Foo lines
Bar, 1.2
...
Every single time the data set is not large, but the task is quite repetitive.
I tried to convert it into an array of hashes, finding uniq names, and then use inject, but somehow it got quite complicated and did not work. Also, looping through everything seems not to be the ideal approach.
Does anyone have a nice and easy idea or solution I am missing? (I only found "Extract value from row in csv and sum it" for PHP.)
First of all, you can use Ruby's CSV library to parse and convert your CSV data:
require 'csv'
csv_data = "Foo, 1.5\nBar, 1.2\nFoo, 1.1"
data_array = CSV.parse(csv_data, converters: :numeric)
#=> [["Foo", 1.5], ["Bar", 1.2], ["Foo", 1.1]]
To sum the values I'd use a hash along with each_with_object:
data_array.each_with_object(Hash.new(0)) { |(k, v), h| h[k] += v }
#=> {"Foo"=>2.6, "Bar"=>1.2}
Passing 0.0 as the default option for your Hash accounts nicely for the first occurrence of each item:
input = [ ['Foo', 1.5],
['Bar', 1.2],
['Foo', 1.1] ]
result = input.inject(Hash.new(0.0)) do |sum, (key, value)|
sum[key] += value
sum
end
p result
The array of hash seems to be the easiest approach:
Let's say that:
CSV=[["foo",1.5],["bar",2.2],["foo",1.1]]
Just do:
myCSV=[["foo",1.5],["bar",1.2],["foo",1.1]]
myCSV.each_with_object(Hash.new(0.0)){|row,sum| sum[row[0]]+=row[1]}
=> {
"foo" => 2.6,
"bar" => 1.2
}
If you are reading from a file, it's more or less the same using the CSV library:
sum=Hash.new(0.0)
CSV.foreach("path/to/file.csv") do |row|
sum[row[0]]+=row[1]
end

Is there a built-in Ruby method for "reshaping" a hash?

I have this hash which I retrieve from a database:
original_hash = {
:name => "Luka",
:school => {
:id => "123",
:name => "Ieperman"
},
:testScores => [0.8, 0.5, 0.4, 0.9]
}
I'm writing an API and want to return a slightly different hash to the client:
result = {
:name => "Luka",
:schoolName => "Ieperman",
:averageScore => 0.65
}
This doesn't work because the method reshape doesn't exist. Does it exist by another name though?
result = original_hash.reshape do |hash|
{
:name => hash[:name],
:school => hash[:school][:name],
:averageScore => hash[:testScores].reduce(:+).to_f / hash[:testScores].count
}
end
I'm new to Ruby so thought I'd ask before I go off overriding core classes. I'm sure it must exist as I always find myself reshaping hashes when writing an API. Or am I totally missing something?
The implementation is dead simple but, like I said, I don't want to override Hash if I don't need to:
class Hash
def reshape
yield(self)
end
end
BTW, I know about this:
result = {
:name => original_hash[:name],
:school => original_hash[:school][:name],
:averageScore => original_hash[:testScores].reduce(:+).to_f / original_hash[:testScores].count
}
But sometimes I don't have an original_hash variable and instead I'm operating straight off a return value, or I'm inside a one liner where this block based approach would be convenient.
Real World example:
#get the relevant user settings from the database, and reshape the hash into the form we want
settings = users.find_one({:_id => oid(a[:userID])}, {:emailNotifications => 1, :newsletter => 1, :defaultSocialNetwork => 1}).reshape do |hash|
{
:emailNotifications => hash[:emailNotifications] == 1,
:newsletter => hash[:newsletter] == 1,
:defaultSocialNetwork => hash[:defaultSocialNetwork]
}
end rescue fail
If you're using Ruby >= 1.9, try a combination of Object#tap and Hash#replace
def foo(); { foo: "bar" }; end
foo().tap { |h| h.replace({original_foo: h[:foo]}) }
# => { :original_foo => "bar" }
Since Hash#replace works in-place, you might find this a bit safer:
foo().clone.tap { |h| h.replace({original_foo: h[:foo]}) }
But this is getting a bit noisy. I'd probably go ahead and monkey-patch Hash at this stage.
From an API perspective, you may be looking for a representer object to sit between your internal model, and the API representation (prior to format-based serialisation). This doesn't work using the shortest, convenient Ruby syntax inline for a hash, but is a nice declarative approach.
For instance, the Grape gem (other API frameworks are available!) might solve the same real-world problem as:
# In a route
get :user_settings do
settings = users.find_one({:_id => oid(a[:userID])}, {:emailNotifications => 1, :newsletter => 1, :defaultSocialNetwork => 1})
present settings, :with => SettingsEntity
end
# Wherever you define your entities:
class SettingsEntity < Grape::Entity
expose( :emailNotifications ) { |hash,options| hash[:emailNotifications] == 1 }
expose( :newsletter ) { |hash,options| hash[:newsletter] == 1 }
expose( :defaultSocialNetwork ) { |hash,options| hash[:defaultSocialNetwork] }
end
This syntax is more geared towards handling ActiveRecord, or similar models, and not hashes though. So not a direct answer to your question, but I think implied by you building up an API. If you put in a representer layer of some kind now (not necessarily grape-entity), you will be thankful for it later, as you'll be better able to manage your model-to-API data mappings when they need to change.
You can replace the call to "reshape" with the builtin method Object#instance_eval and it will work exactly as such. Note however that there may be some unexpected behavior since you evaluating code in the context of the receiving object (e.g. if using "self").
result = original_hash.instance_eval do |hash|
# ...
This abstraction does not exist in the core but people uses it (with different names, pipe, into, as, peg, chain, ...). Note that this let-abstraction is useful not only for hashes, so add it to the class Object.
Is there a `pipe` equivalent in ruby?
if you put your hashes in a array you could use the map function to convert the entries
I can't think of anything that will do this magically, since you're essentially wanting to remap an arbitrary data structure.
Something you may be able to do is:
require 'pp'
original_hash = {
:name=>'abc',
:school => {
:name=>'school name'
},
:testScores => [1,2,3,4,5]
}
result = {}
original_hash.each {|k,v| v.is_a?(Hash) ? v.each {|k1,v1| result[ [k.to_s, k1.to_s].join('_') ] = v1 } : result[k] = v}
result # {:name=>"abc", "school_name"=>"school name", :testScores=>[1, 2, 3, 4, 5]}
but this is incredibly messy and I'd personally be unhappy with it. Performing a manual transform on known keys is probaby better and more maintainable.
Check Facets Hash extensions.

Finding hashes with items from the array using ruby

Sorry for missunderstanding for my fault I didn't check class of item...
I have an array:
array = [link1, link2, link3, link4, etc]
and array_of_hashes with two items: names and links
hash = [ :names, :links ] e.g.
array_of_hashes = [{ :names => name1, :links => link1}, {:names = name2, :links => link2}, ... ]
I want to do something with each pair of items from array_of_hashes which includes links from the array.
UPD: Revised data... sorry for missunderstanding.
It was a bit vague of a question but here is a shot at it.
you will need to reorder what you're trying to access from the hash, unless :name is required.
arr = ["link0", "link1",..."linkN"]
hsh = { "link0", item0, "link1", item1, "link1", item2,..."linkN", itemN}
hsh.each_pair | link, item |
do_something_foo(item) if arr.include?(link) # do_something_foo is a predefined function
end
jagga99 wrote
I want to do something with each item from hash which contain links from the array. Thanks a lot for your help.
If the [:name, :link] is the require pair, then you would need to identify which part is the item to do something with: the name or the link.
Enumerate the array of hashes and search the link array.
array = [:link1, :link2, :link3]
array_of_hashes = [{ :names => :name1, :links => :no_link}, {:names => :name2, :links => :link2}]
array_of_hashes.each do |hash|
if array.any? {|s| s==hash[:links]}
puts "Do something with #{hash[:names].to_s}"
end
end

Convert array-of-hashes to a hash-of-hashes, indexed by an attribute of the hashes

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.

Resources