From: Steve Howell Date: 2010-10-15T00:05:15+09:00 Subject: Re: sort_by: multiple fields with reverse sort On Oct 13, 12:46 am, Rahul Kumar wrote: > I need to use *sort_by* to sort a table, since the user could select > columns in any order. > >     b=[ ["radio", 30, 5], ["radio", 20, 5], ["archie", 20, 5], > ["newton", 10, 3] ] >     b.sort_by{|x| [ x[1], x[0] ]} > > Works fine. However, often the user will want a reverse sort on some > field. I do not off-hand know the datatype of the field, but in most > cases it will be a String. > > I tried: > >     b.sort_by{|x| [ !x[1] ]} > This works (one criteria), but this does not: >     b.sort_by{|x| [ !x[1], x[0] ]} > > Another thread here, suggested using a minus for Numbers but what about > Strings. Here is a solution that allows you to lazily evaluate keys and specify that certain keys are to be reversed. module Enumerable def sort_by(*key_methods) # allow for multiple key_methods and only # evaluate them when they are truly needed # for the sort def compare(a,b, key_methods) i = 0 while i < key_methods.size do for elem in [a, b] do fields = elem[0] key = elem[1] if fields.size <= i fields[i] = key_methods[i][0].call(key) end end if key_methods[i][1] == :desc a, b = b, a end result = (a[0][i] <=> b[0][i]) return result unless result == 0 i += 1 end return result end self.collect do |item| [ [], item ] end.sort do |a, b| compare(a, b, key_methods) end.collect do |kv| kv[1] end end end a = [ ["radio", 30, 5], ["radio", 40, 5], ["radio", 20, 5], ["archie", 20, 5], ["newton", 10, 3] ] puts a.sort_by( [Proc.new { |a| a[0] }], [Proc.new { |a| a[1] }, :desc] ).inspect