JSON.parse changes sorting - Ruby - ruby

In the code below, the order of my items gets changed after the JSON.parse(f) line, i.e., this hash:
{
a => aval,
b => bval,
c => cval,
d => dval
}
becomes something like:
{
b => bval,
c => cval,
a => aval,
d => dval
}
This is a problem because my display code just reads from the json file, so any time I save back to it, and then display, everything gets changed around. Is there anything I can do to retain the order?
CODE:
f = File.read($PLAN_DESC_PATH)
puts ("f " + f.to_s())
hash = JSON.parse(f)
puts ("hash " + hash.to_s())
My Ruby version is 1.8.7. I am using Sinatra. I believe I got the JSON gem from here: http://flori.github.com/json/ (sorry, kinda new to this). Thanks!

In Ruby 1.8.7 the Hash class does not maintain order either by keys or by order added. If you need something like that, you would need to implement something like ActiveSupport::OrderedHash (http://rubydoc.info/docs/rails/ActiveSupport/OrderedHash)
In Ruby 1.9.x hashes are ordered by when they are inserted by default (see http://www.ruby-doc.org/core/classes/Hash.html)
When you serialize a hash to JSON, all bets are off for maintaining order of your keys. You'll need some post processing after your serialization to ensure order if that's necessary for you.

No, hashmaps are not meant to have a specific ordering. If you need ordering use something different like an array. Or extract all the keys, sort them like you want and then you can have what order you like.
Making assumptions on ordering inside maps is anyway something on which you shouldn't rely, that's the fact.
A good alternative would be to have:
[ [a, aval], [b, bval], ... ]

Jack answered for Ruby, so I'll answer for JSON. From RFC 4627 (emphasis added):
"An object is an unordered collection of zero or more name/value pairs"

Related

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}

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.

Rails: control ordering of query parameters in url_for?

I'd like to generate a URL where the "p=1" query param appears at the end of the URL, like:
/path?foo=X&bar=Y&p=1
Is it possible to control the ordering of query parameters when generating URLs via:
url_for(params.merge({ p: page_num }))
?
Update:
I tried ChuckE's suggestion below. It turns out that in Ruby 1.9 Hashes are already ordered, so the code in ActiveSupport::OrderedHash is effectively no-op'd. You can verify with Ruby 1.9 that order is preserved:
>> h = {one: 1, two: 2, three: 3 }
{:one=>1, :two=>2, :three=>3}
>> f = h.except(:one)
{:two=>2, :three=>3}
>> f[:one] = 1
1
>> f
{:two=>2, :three=>3, :one=>1}
However, url_for still puts the "p" param first. It seems that any potential solution will need to address how url_for iterates the hash.
After further digging, I see that what's happening is that url_for is actually sorting the parameters by key lexicographically, independent of their insertion order in the hash. Apparently this is being done to aid caching, since URL params are often used for page cache keys.
In short, you can't do it without patching Hash, specifically, you need to override activesupport/core_ext/object/to_param.rb so that Hash#to_param does not call .sort on the return value.
Related question: How to generate custom sorted query string URL in Rails link_to?.
First question is: why would you need something like that? The order which the parameters appear in the url in doesn't influence the way they are fetched by the server, since they are basic key/value associations. So, no matter where the parameter appears, it will always be recognized by the server.
Nonetheless, to answer your question, yes, it is possible. You just have to use ordered hashes. They are available through active support.
opts = OrderedHash.new
opts[:foo] = 'X'
opts[:bar] = 'Y'
opts[:p] = 1
your_helper_url(opts)
Should do the trick for you.

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