I have an array of hashes:
[ { :foo => 'foo', :bar => 2 }, { :foo => 'foo', :bar => 3 }, { :foo => 'foo', :bar => 5 }, ]
I am trying to sort this array in descending order according to the value of :bar
in each hash.
I am using sort_by
to sort above array:
a.sort_by { |h| h[:bar] }
However, this sorts the array in ascending order. How do I make it sort in descending order?
One solution was to do following:
a.sort_by { |h| -h[:bar] }
But that negative sign does not seem appropriate.
To sort an array in Java in descending order, you have to use the reverseOrder() method from the Collections class. The reverseOrder() method does not parse the array. Instead, it will merely reverse the natural ordering of the array.
The Ruby sort method works by comparing elements of a collection using their <=> operator (more about that in a second), using the quicksort algorithm. You can also pass it an optional block if you want to do some custom sorting. The block receives two parameters for you to specify how they should be compared.
You can use the sort method on an array, hash, or another Enumerable object & you'll get the default sorting behavior (sort based on <=> operator) You can use sort with a block, and two block arguments, to define how one object is different than another (block should return 1, 0, or -1)
It's always enlightening to do a benchmark on the various suggested answers. Here's what I found out:
#!/usr/bin/ruby require 'benchmark' ary = [] 1000.times { ary << {:bar => rand(1000)} } n = 500 Benchmark.bm(20) do |x| x.report("sort") { n.times { ary.sort{ |a,b| b[:bar] <=> a[:bar] } } } x.report("sort reverse") { n.times { ary.sort{ |a,b| a[:bar] <=> b[:bar] }.reverse } } x.report("sort_by -a[:bar]") { n.times { ary.sort_by{ |a| -a[:bar] } } } x.report("sort_by a[:bar]*-1") { n.times { ary.sort_by{ |a| a[:bar]*-1 } } } x.report("sort_by.reverse!") { n.times { ary.sort_by{ |a| a[:bar] }.reverse } } end user system total real sort 3.960000 0.010000 3.970000 ( 3.990886) sort reverse 4.040000 0.000000 4.040000 ( 4.038849) sort_by -a[:bar] 0.690000 0.000000 0.690000 ( 0.692080) sort_by a[:bar]*-1 0.700000 0.000000 0.700000 ( 0.699735) sort_by.reverse! 0.650000 0.000000 0.650000 ( 0.654447)
I think it's interesting that @Pablo's sort_by{...}.reverse!
is fastest. Before running the test I thought it would be slower than "-a[:bar]
" but negating the value turns out to take longer than it does to reverse the entire array in one pass. It's not much of a difference, but every little speed-up helps.
Please note that these results are different in Ruby 1.9
Here are results for Ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin10.8.0]:
user system total real sort 1.340000 0.010000 1.350000 ( 1.346331) sort reverse 1.300000 0.000000 1.300000 ( 1.310446) sort_by -a[:bar] 0.430000 0.000000 0.430000 ( 0.429606) sort_by a[:bar]*-1 0.420000 0.000000 0.420000 ( 0.414383) sort_by.reverse! 0.400000 0.000000 0.400000 ( 0.401275)
These are on an old MacBook Pro. Newer, or faster machines, will have lower values, but the relative differences will remain.
Here's a bit updated version on newer hardware and the 2.1.1 version of Ruby:
#!/usr/bin/ruby require 'benchmark' puts "Running Ruby #{RUBY_VERSION}" ary = [] 1000.times { ary << {:bar => rand(1000)} } n = 500 puts "n=#{n}" Benchmark.bm(20) do |x| x.report("sort") { n.times { ary.dup.sort{ |a,b| b[:bar] <=> a[:bar] } } } x.report("sort reverse") { n.times { ary.dup.sort{ |a,b| a[:bar] <=> b[:bar] }.reverse } } x.report("sort_by -a[:bar]") { n.times { ary.dup.sort_by{ |a| -a[:bar] } } } x.report("sort_by a[:bar]*-1") { n.times { ary.dup.sort_by{ |a| a[:bar]*-1 } } } x.report("sort_by.reverse") { n.times { ary.dup.sort_by{ |a| a[:bar] }.reverse } } x.report("sort_by.reverse!") { n.times { ary.dup.sort_by{ |a| a[:bar] }.reverse! } } end # >> Running Ruby 2.1.1 # >> n=500 # >> user system total real # >> sort 0.670000 0.000000 0.670000 ( 0.667754) # >> sort reverse 0.650000 0.000000 0.650000 ( 0.655582) # >> sort_by -a[:bar] 0.260000 0.010000 0.270000 ( 0.255919) # >> sort_by a[:bar]*-1 0.250000 0.000000 0.250000 ( 0.258924) # >> sort_by.reverse 0.250000 0.000000 0.250000 ( 0.245179) # >> sort_by.reverse! 0.240000 0.000000 0.240000 ( 0.242340)
New results running the above code using Ruby 2.2.1 on a more recent Macbook Pro. Again, the exact numbers aren't important, it's their relationships:
Running Ruby 2.2.1 n=500 user system total real sort 0.650000 0.000000 0.650000 ( 0.653191) sort reverse 0.650000 0.000000 0.650000 ( 0.648761) sort_by -a[:bar] 0.240000 0.010000 0.250000 ( 0.245193) sort_by a[:bar]*-1 0.240000 0.000000 0.240000 ( 0.240541) sort_by.reverse 0.230000 0.000000 0.230000 ( 0.228571) sort_by.reverse! 0.230000 0.000000 0.230000 ( 0.230040)
Updated for Ruby 2.7.1 on a Mid-2015 MacBook Pro:
Running Ruby 2.7.1 n=500 user system total real sort 0.494707 0.003662 0.498369 ( 0.501064) sort reverse 0.480181 0.005186 0.485367 ( 0.487972) sort_by -a[:bar] 0.121521 0.003781 0.125302 ( 0.126557) sort_by a[:bar]*-1 0.115097 0.003931 0.119028 ( 0.122991) sort_by.reverse 0.110459 0.003414 0.113873 ( 0.114443) sort_by.reverse! 0.108997 0.001631 0.110628 ( 0.111532)
...the reverse method doesn't actually return a reversed array - it returns an enumerator that just starts at the end and works backwards.
The source for Array#reverse
is:
static VALUE rb_ary_reverse_m(VALUE ary) { long len = RARRAY_LEN(ary); VALUE dup = rb_ary_new2(len); if (len > 0) { const VALUE *p1 = RARRAY_CONST_PTR_TRANSIENT(ary); VALUE *p2 = (VALUE *)RARRAY_CONST_PTR_TRANSIENT(dup) + len - 1; do *p2-- = *p1++; while (--len > 0); } ARY_SET_LEN(dup, RARRAY_LEN(ary)); return dup; }
do *p2-- = *p1++; while (--len > 0);
is copying the pointers to the elements in reverse order if I remember my C correctly, so the array is reversed.
Just a quick thing, that denotes the intent of descending order.
descending = -1 a.sort_by { |h| h[:bar] * descending }
(Will think of a better way in the mean time) ;)
a.sort_by { |h| h[:bar] }.reverse!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With