Ruby Sort_by multiple parameters opposite asc/desc

Viewed 234

Trying to sort by multiple parameters but having trouble with one parameter sorting ascendingly and the other descending.

array = [20, 10, 40, 1200, 300]

I need to first sort the array by the number of digits in each number descending.

So we get [1200, 120, 40, 10, 20]

If there is a tie between lengths, I need to sort them ascendingly by value

So we get: [1200, 120, 10, 20, 40]

I'm trying to make sort_by work for this but I can't figure out how to sort the first parameter descending and the second ascendingly.

def digit_sorter(ar) 
   ar.sort_by {|num| [num.to_s.length, num]}.reverse 
end

I've also tried [ [num.to_s.length], [num].reverse ]

2 Answers
# length desc, value asc
def digit_sorter(nums)
  nums.sort { |a, b| [b.to_s.length, a] <=> [a.to_s.length, b] }
end


# length asc, value desc
def digit_sorter(nums)
  nums.sort { |a, b| [a.to_s.length, b] <=> [b.to_s.length, a] }
end

input same array [345, 23, 34, 1], the output respectively are

# length desc, value asc
[345, 23, 34, 1]

# value desc, length asc
[1, 34, 23, 345]

Always prefer sort_by over sort when speed may be an issue and sort keys are expensive to compute.
For ascending order, just use the sort key as is, here a (value of the array element). For descending order, use -key (minus key), here -a.to_s.length (the length of the array element):

array = [20, 10, 40, 1200, 300]
puts array.sort_by { |a| [-a.to_s.length, a] }

Prints:

1200
300
10
20
40

Which is faster: sort_by or sort?

sort_by [is] fairly expensive when the keysets are simple. ... However, consider the case where comparing the keys is a non-trivial operation. A more efficient technique is to cache the sort keys ... before the sort. Perl users often call this approach a Schwartzian transform, after Randal Schwartz. ... This is exactly what sort_by does internally.

(from sort_by docs)

SEE ALSO

Brandon Dimcheff: "Ruby's sort vs. sort_by"


Benchmarking sort_by vs. sort:

For the case described by the OP, sort_by is slightly faster than sort, although both are fast, because the array is small (size = 5, see below). As the array grows to more realistic size=10, 100, 1000, sort_by is about twice as fast, due to the reduced need of the expensive sort key computations. Note that relative performance is heavily dependent on the array size and how expensive are the sort keys to compute, so treat this as a single data point and do not overgeneralize these results.

Code:

#!/usr/bin/env ruby

require 'benchmark' 

max_val = 2_000

[5, 10, 100, 1000].each do |size|
  puts "###"
  puts "array size=#{size}:"
  puts "###"
  Benchmark.bmbm do |x|
    Kernel.srand(1234)
    x.report("sort_by")    { 10000.times { (1..size).to_a.map { rand(max_val) } .sort_by { |a| [-a.to_s.length, a] }  } }
    Kernel.srand(1234)
    x.report("sort") { 10000.times { (1..size).to_a.map { rand(max_val) } .sort { |a, b| [b.to_s.length, a] <=> [a.to_s.length, b] } } } 
  end
end

Results:

###
array size=5:
###
Rehearsal -------------------------------------------
sort_by   0.070000   0.000000   0.070000 (  0.062603)
sort      0.070000   0.000000   0.070000 (  0.074214)
---------------------------------- total: 0.140000sec

              user     system      total        real
sort_by   0.060000   0.000000   0.060000 (  0.061337)
sort      0.070000   0.000000   0.070000 (  0.070706)
###
array size=10:
###
Rehearsal -------------------------------------------
sort_by   0.110000   0.000000   0.110000 (  0.117958)
sort      0.190000   0.000000   0.190000 (  0.183992)
---------------------------------- total: 0.300000sec

              user     system      total        real
sort_by   0.110000   0.000000   0.110000 (  0.116410)
sort      0.180000   0.000000   0.180000 (  0.184045)
###
array size=100:
###
Rehearsal -------------------------------------------
sort_by   1.770000   0.010000   1.780000 (  1.776254)
sort      3.800000   0.000000   3.800000 (  3.797437)
---------------------------------- total: 5.580000sec

              user     system      total        real
sort_by   1.780000   0.000000   1.780000 (  1.783407)
sort      3.860000   0.000000   3.860000 (  3.865884)
###
array size=1000:
###
Rehearsal -------------------------------------------
sort_by  25.380000   0.010000  25.390000 ( 25.403899)
sort     60.290000   0.040000  60.330000 ( 60.374564)
--------------------------------- total: 85.720000sec

              user     system      total        real
sort_by  26.010000   0.060000  26.070000 ( 26.122175)
sort     60.990000   0.060000  61.050000 ( 61.134355)
Related