Sort in ruby a JSON array of hashes - ruby

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}

Related

How to sort an array in Ruby

Persoane = []
Nume = gets
Persoane.push Nume.split(",")
puts Persoane.sort
I am trying to get an user to input carachters that get split into substrings which get inserted in an array, then the program would output the strings in alphabetical order. It doesnt seem to work and I just get the array's contents, like so:
PS C:\Users\Lenovo\Desktop\Ruby> ruby "c:\Users\Lenovo\Desktop\Ruby\ruby-test.rb"
Scrie numele la persoane
Andrei,Codrin,Bradea
Andrei
Codrin
Bradea
PS C:\Users\Lenovo\Desktop\Ruby>
you can do this :
Nume = gets
puts Nume.split(",").sort
or in 1 line
array = gets.chomp.split(",").sort
The error is because of your use of push. Let's assume that you define the constant Nume by
Nume='Andrei,Codrin,Bradea'
Then, Nume.split(',') would return the Array ['Andrei', 'Codrin', 'Bradea']. When you do a Persoane.push, the whole array is added to your array Persoane as a single element. Therefore, Persoane contains only one Element, as you can verify when you do a
p Persoane
If you sort a one-element array, the result will also be just that one element - there is nothing to sort.
What you can do is using concat instead of push. This would result in Persoane being a 3-element array which can be sorted.
I'm not sure you need use constants here
If you don't need keep user input and use it somewhere, you can just chain methods like this
persons = gets.chomp.split(",").sort
For something a little different, let's not split at all.
people = gets.scan(/[^,]+/).map(&:strip).sort
This will avoid problems like multiple commas in a row yielding empty strings. Of course, you could also avoid that with:
people = gets.split(/\,+/).map(&:strip).sort

How to return array of hashes with modified values

I've been successfully converting an array of objects into an array of hashes. But I also want to modify the objects slightly as well, before getting the combined hash.
This is what I do to convert array of objects into a combined hash:
prev_vars.map(&:to_h).reduce({}, :merge)
{ "b"=>#<Money fractional:400 currency:GBP> }
But what I want to have instead, which required to additionally call to_i is:
{ "b"=> 4 }
I got this working using this line, but I am looking for a more elegant solution:
prev_vars.map(&:to_h).reduce({}) { |combined, v| combined.merge({v.keys[0] => v.values[0].to_i}) }
How large is prev_vars? map(&:to_h) could require a fair amount of memory overhead, because it instantiates an entirely new array. Instead, I'd recommend switching the order: first #reduce, then #to_h:
prev_vars.reduce({}) do |combined, var|
combined.merge! var.to_h.transform_values!(&:to_i)
end
Note the use of #merge! rather than #merge so that a new hash is not created for combined for each iteration of the loop.

Parse text/json data in Ruby

I am collecting HTTP response and it comes back in the text/json form. The original format is as follows:
{"param" => "value", "interesting_param" => [{"parama1"=>vala1,"parama2"=>vala2,"parama3"=>vala3,"parama4"=>vala4,"parama5"=>vala5},
{"paramb1"=>valb1,"paramb2"=>valb2,"paramb3"=>valb3,"paramb4"=>valb4,"paramb5"=>valb5}]}
When I do a JSON.parse(response.body)["interesting_param"], I can retrieve this output:
{"parama1"=>vala1,"parama2"=>vala2,"parama3"=>vala3,"parama4"=>vala4,"parama5"=>vala5},
{"paramb1"=>valb1,"paramb2"=>valb2,"paramb3"=>valb3,"paramb4"=>valb4,"paramb5"=>valb5}
How can I capture only the following from the full result-set above.
`parama1-vala1`, `parama2-vala2` and `parama5-vala5`
`paramb1-valb1`, `paramb2-valb2` and `paramb5-valb5`
Update
I did try further on this & now I am thinking of making use of loop.
The way I am attempting to do this is:
Find the count of records, for example, if:
test =
{"parama1"=>vala1,"parama2"=>vala2,"parama3"=>vala3,"parama4"=>vala4,"parama5"=>vala5},
{"paramb1"=>valb1,"paramb2"=>valb2,"paramb3"=>valb3,"paramb4"=>valb4,"paramb5"=>valb5}
Then, test.count will be 2.
Now if somehow I can use a loop to iterate over elements in test, then I might be able to capture specific elements.
Thanks.
It looks like you want to map each hash into a list of strings made by joining the string version of the key with the string version of the value, joined by a '-'.
JSON.parse(response.body)["interesting_param"]
The above code should give you a ruby list of hashes.
interesting_bits = JSON.parse(response.body)["interesting_param"]
result = interesting_bits.map{|bit| bit.map{|k,v| "#{k}-#{v}"}}
Something like that should do the trick.
puts result.inspect
#prints
# [ ["parama1-vala1","parama2-vala2","parama3-vala3","parama4-vala4","parama5-vala5"] , ["paramb1-valb1","paramb2-valb2","paramb3-valb3","paramb4-valb4","paramb5-valb5"] ]
I don't understand what criteria you are using for then filtering this down to just 1,2 and 5... but that is easily done too. I would do that to the hashes before converting them to string lists.

Sort a hash Ruby

I have a hash that looks like this
h1 = {"4c09a0da6071a593f051de32"=>["4c09a0da6071a593f051de32", "Cafe Bistro", 37.78458803130115, -122.40743637084961, 215.0], "4abbb03ef964a520668420e3"=>["4abbb03ef964a520668420e3", "The Plant Cafe Organic", 37.7977805076241, -122.3957633972168, 83.0] }
I would like to sort it by the final value in each hash e.g. 83.0, 215.0
I have tried
h1 = h1.sort_by{|k,v| v[4]}
but in out puts an array not a hash, i would like to keep the hash the same just reordered... how do I do this?
It's not a great idea to count on ordering in a Hash. Ruby didn't order hashes at all in 1.8. The data structure in its canonical form is not ordered.
It's better style to use an Array when ordering is important and a Hash or something else when key lookup is needed.
There is a grey area when writing tests. In that case, it may be reasonable to depend on Hash ordering since you are testing a specific Ruby program in certain conditions and you have, after all, a test that can fail should the implementation assumptions ever change.
You need to convert the array back to a hash:
h1 = Hash[h1.sort_by { |_,v| v[-1] }]
Note that this only works since Ruby 1.9. Before that, hashes were not an ordered data structure.

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