Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sort an array in descending order in Ruby

Tags:

sorting

ruby

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.

like image 655
Waseem Avatar asked Apr 15 '10 01:04

Waseem


People also ask

How do you sort an array in descending order?

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.

How can you sort an array Ruby?

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.

How do you sort an array of hashes in Ruby?

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)


2 Answers

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.

like image 178
the Tin Man Avatar answered Sep 30 '22 00:09

the Tin Man


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! 
like image 34
Pablo Fernandez Avatar answered Sep 30 '22 01:09

Pablo Fernandez