aggregate values from specific key in an array of hashes - ruby

I'm trying to build an array of values that come from an array of hashes, at the moment my code looks like this:
ids = array_of_hashes.inject([]) do |result,instance|
result << instance[:id]
result
end
I just want to know if there's a more efficient way to do it?

You could change it to look like:
ids = hash.map { |instance| instance[:id] }
Not necessarily more efficient, but easier to read and maintain!
Good luck!

There are two easy ways for it:
1. ids = hash.collect{|h| h[:id]}
2. ids = hash.map{|h| h[:id]}
Now you would ask what is the difference in both? for explanation see this accepted answer

Related

Extract values from an array of hashes

I need to extract values from an array of hashes:
data =
[{:diaria_media=>"103.58908136482939632545931759",
:room_night=>"1143",
:valor_periodo=>"118402.320000"},
{:diaria_media=>"307.46792079207920792079207921",
:room_night=>"101",
:valor_periodo=>"31054.260000"},
{:diaria_media=>"313.000000",
:room_night=>"9",
:valor_periodo=>"2817.000000"},
{:diaria_media=>"0.0",
:room_night=>"7",
:valor_periodo=>"0.0"},
{:diaria_media=>"4.4630434782608695652173913043",
:room_night=>"414",
:valor_periodo=>"1847.700000"},
{:diaria_media=>"150.89382627422828427853553482",
:room_night=>"1393",
:valor_periodo=>"210195.100000"},
{:diaria_media=>"221.11425992779783393501805054",
:room_night=>"554",
:valor_periodo=>"122497.300000"},
{:diaria_media=>"36.919200",
:room_night=>"25",
:valor_periodo=>"922.980000"},
{:diaria_media=>"31.967530864197530864197530864",
:room_night=>"81",
:valor_periodo=>"2589.370000"},
{:diaria_media=>"0",
:room_night=>"0",
:valor_periodo=>"0.000000"}]
I need to get all the :room night fields and add the values. What is the best way to achieve that?
first: it's not nice to ask for help and to format the question as you did.
second: the question has nothing to do with Rails or with Savon.
This is a pure Ruby question.
The solution seems simple to me.
You iterate over your array and summarize the numbers for each key :room_night
For example like this:
nights = 0
data.each do |booking|
nights += booking[:room_night].to_i
end
print "nights=#{nights}\n"
If you go functional it's even more simple:
nights = data.map{|e| e[:room_night].to_i}.reduce(:+)
and done!
As a bonus I put a executable script into Pastbin https://pastebin.com/29nMTYrK

What is the most elegant way to sort ranges in ruby

I need to sort a table of objects of type Rangeby their start point. For that I have the following code which works fine:
ranges = #ranges.sort do |a,b|
(a.min) <=> (b.min)
end
I was just wondering if there was a shorter and elegant way to do the same thing.
How about:
ranges = #ranges.sort_by(&:min)
Or if you actually mean the starting point rather than the minimum, since ranges such as (5..3) can exist:
ranges = #ranges.sort_by(&:first)

Sort in ruby a JSON array of hashes

So I have an array of hashes:
[{"id":"30","name":"Dave"},
{"id":"57","name":"Mike"},
{"id":"9","name":"Kevin"},
...
{"id":"1","name":"Steve"}]
And I want to sort it by the id attribute, so that it looks like this:
[{"id":"1","name":"Steve"},
{"id":"2","name":"Walter"},
...
{"id":"60","name":"Chester"}]
I'm assuming I use the sort_by method but I'm not exactly sure how to do it.
This should work:
array.sort_by { |hash| hash['id'].to_i }
In this case, sort_by is preferred over sort because it is more efficient. While sort calls to_i on every comparison, sort_by does it once for each element in array and remembers the result.
When I see incoming data like that, it's almost always a JSON string. Ruby doesn't automatically understand JSON, nor does it automatically know how to convert it, but Ruby does make it easy for us to convert from/to it:
require 'json'
json_data = '[{"id":"30","name":"Dave"},
{"id":"57","name":"Mike"},
{"id":"9","name":"Kevin"},
{"id":"1","name":"Steve"}]'
ary = JSON[json_data].sort_by{ |e| e['id'].to_i }
ary
# => [{"id"=>"1", "name"=>"Steve"}, {"id"=>"9", "name"=>"Kevin"}, {"id"=>"30", "name"=>"Dave"}, {"id"=>"57", "name"=>"Mike"}]
The only real trick here is:
JSON[json_data]
A lot of time you'll see people use JSON.parse(json_data), but the [] method is smart enough to recognize whether it's getting a String or an array or a hash. If it's a string it tries to parse it assuming it's incoming data. If it's an array or a hash, it converts it to a JSON string for output. The result is, using JSON[...] simplifies the use of the class and makes it so we don't have to use parse or to_json.
Otherwise, using sort_by is preferred over using sort unless you are directly comparing two simple variables, like integer to integer, string to string or character to character. Once you have to dive into an object, or do some sort of calculation to determine how things compare, then you should use sort_by. See Wikipedia's article on Schwartzian Transform to understand what's going on under the covers. It's a very powerful technique that can speed up sorting remarkably.
Your Hash syntax is wrong, if they where symbols then it would look like this:
data = [
{id:"30", name:"Dave"},
{id:"57", name:"Mike"},
{id:"9", name:"Kevin"},
{id:"1", name:"Steve"}
]
sorted_data = data.sort_by{|x| x[:id].to_i}
Edit: Forgot the to_i, fixed. If the keys are strings the : way of defining a hash does not work, so we need hash-rockets instead:
data = [{"id"=>"30","name"=>"Dave"},
{"id"=>"57","name"=>"Mike"},
{"id"=>"9","name"=>"Kevin"},
{"id"=>"1","name"=>"Steve"}]
sorted_data = data.sort_by{|x| x['id'].to_i}

Ruby: how to find the next match in an array

I have to search an item in an array and return the value of the next item. Example:
a = ['abc.df','-f','test.h']
i = a.find_index{|x| x=~/-f/}
puts a[i+1]
Is there any better way other than working with index?
A classical functional approach uses no indexes (xs.each_cons(2) -> pairwise combinations of xs):
xs = ['abc.df', '-f', 'test.h']
(xs.each_cons(2).detect { |x, y| x =~ /-f/ } || []).last
#=> "test.h"
Using Enumerable#map_detect simplifies it a litte bit more:
xs.each_cons(2).map_detect { |x, y| y if x =~ /-f/ }
#=> "test.h"
The reason something like array.find{something}.next doesn't exist is that it's an array rather than a linked list. Each item is just it's own value; it doesn't have a concept of "the item after me".
#tokland gives a good solution by iterating over the array with each pair of consecutive items, so that when the first item matches, you have your second item handy. There are strong arguments to be made for the functional style, to be sure. Your version is shorter, though, and I'd argue that yours is also more quickly and easily understood at a glance.
If the issue is that you're using it a lot and want something cleaner and more to the point, then of course you could just add it as a singleton method to a:
def a.find_after(&test)
self[find_index(&test).next]
end
Then
a.find_after{|x| x=~/-f/}
is a clear way to find the next item after the first match.
All of that said, I think #BenjaminCox makes the best point about what appears to be your actual goal. If you're parsing command line options, there are libraries that do that well.
I don't know of a cleaner way to do that specific operation. However, it sure looks like you're trying to parse command-line arguments. If so, I'd recommend using the built-in OptionParser module - it'll save a ton of time and hair-pulling trying to parse them yourself.
This article explains how it works.
Your solution working with indexes is fine, as others have commented. You could use Enumerable#drop_while to get an array from your match on and take the second element of that:
a = ['abc.df','-f','test.h']
f_arg = a.drop_while { |e| e !~ /-f/ }[1]

How do you modify array mapping data structure resultant from Ruby map?

I believe that I may be missing something here, so please bear with me as I explain two scenarios in hopes to reconcile my misunderstanding:
My end goal is to create a dataset that's acceptable by Highcharts via lazy_high_charts, however in this quest, I'm finding that it is rather particular about the format of data that it receives.
A) I have found that when data is formatted like this going into it, it draws the points just fine:
[0.0000001240,0.0000000267,0.0000000722, ..., 0.0000000512]
I'm able to generate an array like this simply with:
array = Array.new
data.each do |row|
array.push row[:datapoint1].to_f
end
B) Yet, if I attempt to use the map function, I end up with a result like and Highcharts fails to render this data:
[[6.67e-09],[4.39e-09],[2.1e-09],[2.52e-09], ..., [3.79e-09]]
From code like:
array = data.map{|row| [(row.datapoint1.to_f)] }
Is there a way to coax the map function to produce results in B that more akin to the scenario A resultant data structure?
This get's more involved as I have to also add datetime into this, however that's another topic and I just want to understand this first and what can be done to perhaps further control where I'm going.
Ultimately, EVEN SCENARIO B SHOULD WORK according to the data in the example here: http://www.highcharts.com/demo/spline-irregular-time (press the "View options" button at bottom)
Heck, I'll send you a sucker in the mail if you can fill me in on that part! ;)
You can fix arrays like this
[[6.67e-09],[4.39e-09],[2.1e-09],[2.52e-09], ..., [3.79e-09]]
that have nested arrays inside them by using the flatten method on the array.
But you should be able to avoid generating nested arrays in the first place. Just remove the square brackets from your map line:
array = data.map{|row| row.datapoint1.to_f }
Code
a = [[6.67e-09],[4.39e-09],[2.1e-09],[2.52e-09], [3.79e-09]]
b = a.flatten.map{|el| "%.10f" % el }
puts b.inspect
Output
["0.0000000067", "0.0000000044", "0.0000000021", "0.0000000025", "0.0000000038"]
Unless I, too, am missing something, your problem is that you're returning a single-element array from your block (thereby creating an array of arrays) instead of just the value. This should do you:
array = data.map {|row| row.datapoint1.to_f }
# => [ 6.67e-09, 4.39e-09, 2.1e-09, 2.52e-09, ..., 3.79e-09 ]

Resources