# Sort\_by: multiple fields with reverse sort

**URL:** https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423
**Category:** ruby-talk
**Created:** [13 October 2010 07:46 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423 "2010-10-13T07:46:40Z")
**Posts on this page:** 16
**Page:** 1

<div class="post-metadata">

### Author: ![R\_Kumar](https://avatars.discourse-cdn.com/v4/letter/r/c0e974/32.png) [@R\_Kumar](https://rubytalk.org/u/R_Kumar)
#### Post date: [13 October 2010 07:46 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/1 "2010-10-13T07:46:40Z")

</div>

I need to use \*sort\_by\* to sort a table, since the user could select  
columns in any order.

&nbsp;&nbsp;&nbsp;&nbsp;b=[["radio", 30, 5], ["radio", 20, 5], ["archie", 20, 5],  
["newton", 10, 3] ]  
&nbsp;&nbsp;&nbsp;&nbsp;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:

&nbsp;&nbsp;&nbsp;&nbsp;b.sort\_by{|x| [!x[1] ]}  
This works (one criteria), but this does not:  
&nbsp;&nbsp;&nbsp;&nbsp;b.sort\_by{|x| [!x[1], x[0] ]}

Another thread here, suggested using a minus for Numbers but what about  
Strings.  
Thx, rahul.

> **···**
>
> --  
> Posted via [http://www.ruby-forum.com/](http://www.ruby-forum.com/).

---

<div class="post-metadata">

### Author: ![Stefano\_Crocco](https://avatars.discourse-cdn.com/v4/letter/s/5f9b8f/32.png) [@Stefano\_Crocco](https://rubytalk.org/u/Stefano_Crocco)
#### Post date: [13 October 2010 08:10 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/2 "2010-10-13T08:10:51Z")

</div>

Are you sure the one criterium version works as you think? !x[1] returns a  
boolean value (true if x[1] is false or nil and false otherwise). So, if the  
contents of the arrays are all strings or numbers, it'll compare items by  
comparing equal arrays only containing the element false. Most likely, this  
would give you the elements in the same order.

I don't know how to do what you want using sort\_by, but you can do that using  
sort. For example, the following code does a reverse sort on the 1 index and a  
normal sort on the 0 index

b.sort do |a, b|  
&nbsp;&nbsp;res = -(a[1] \<=\> b[1])  
&nbsp;&nbsp;res = a[0] \<=\> b[0] if res == 0  
&nbsp;&nbsp;res  
end

For more information, look at the documentation for Enumerable#sort and the  
Comparable module (for the \<=\> operator).

I hope this helps

Stefano

> **···**
>
> On Wednesday 13 October 2010, 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.  
> > \>Thx, rahul.

---

<div class="post-metadata">

### Author: ![Y\_NOBUOKA](https://avatars.discourse-cdn.com/v4/letter/y/c2a13f/32.png) [@Y\_NOBUOKA](https://rubytalk.org/u/Y_NOBUOKA)
#### Post date: [13 October 2010 09:08 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/3 "2010-10-13T09:08:44Z")

</div>

The one way to do a reverse sort is wrapping a sort target in a class  
for a reverse sort.  
For instance:

# This class is a class for a reverse sort.  
# An Object of various datatypes can be wrapped in an object of this class.  
class ItemForReverseSort  
&nbsp;&nbsp;def initialize( item )  
&nbsp;&nbsp;&nbsp;&nbsp;@item = item  
&nbsp;&nbsp;end  
&nbsp;&nbsp;def item  
&nbsp;&nbsp;&nbsp;&nbsp;@item  
&nbsp;&nbsp;end  
&nbsp;&nbsp;def \<=\>( target )  
&nbsp;&nbsp;&nbsp;&nbsp;( self.item \<=\> target.item ) \* (-1)  
&nbsp;&nbsp;end  
end

b = [["radio", 30, 5], ["radio", 20, 5], ["archie", 20, 5], ["newton", 10, 3] ]

# normal sort  
p b.sort\_by{|x| [x[1], x[0] ]}  
&nbsp;&nbsp;#=\> [["newton", 10, 3], ["archie", 20, 5], ["radio", 20, 5], ["radio", 30, 5]]

# normal sort for 1st item, and reverse sort for 2nd item  
p b.sort\_by{|x| [x[1], ItemForReverseSort.new(x[0]) ]}  
&nbsp;&nbsp;#=\> [["newton", 10, 3], ["radio", 20, 5], ["archie", 20, 5], ["radio", 30, 5]]

> **···**
>
> 2010/10/13 Rahul Kumar \<sentinel1879@gmail.com\>:
> 
> > 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.  
> > Thx, rahul.
> 
> --  
> NOBUOKA Yuya

---

<div class="post-metadata">

### Author: ![R\_Kumar](https://avatars.discourse-cdn.com/v4/letter/r/c0e974/32.png) [@R\_Kumar](https://rubytalk.org/u/R_Kumar)
#### Post date: [13 October 2010 12:53 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/4 "2010-10-13T12:53:53Z")

</div>

When i sat down to code, i realized that i could not just put the  
sort\_keys list to sort\_by, i would have to unroll the sort\_keys array.  
So, i might as well use sort. This seems to work in the small case i  
have. Could someone comment on this, and suggest cleaner better way to  
do. Thanks.

b=[["radio", 30, 5], ["radio", 20, 5], ["archie", 20, 5], ["newton",  
10, 3] ]  
sort\_keys=[1,0]  
rev\_flag = [true, false]

c=b.sort{|x,y|  
&nbsp;&nbsp;res = 0  
&nbsp;&nbsp;sort\_keys.each\_with\_index { |e,i|  
&nbsp;&nbsp;&nbsp;&nbsp;if rev\_flag[i]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res = y[e] \<=\> x[e]  
&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res = x[e] \<=\> y[e]  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;break if res != 0  
&nbsp;&nbsp;}  
&nbsp;&nbsp;res  
}  
c

> **···**
>
> --  
> Posted via [http://www.ruby-forum.com/](http://www.ruby-forum.com/).

---

<div class="post-metadata">

### Author: ![Steve\_Howell](https://avatars.discourse-cdn.com/v4/letter/s/b4bc9f/32.png) [@Steve\_Howell](https://rubytalk.org/u/Steve_Howell)
#### Post date: [14 October 2010 02:50 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/5 "2010-10-14T02:50:39Z")

</div>

It would be nice if Ruby supported a sort\_by on steroids.

&nbsp;&nbsp;sorted\_list = list.multi\_field\_sort\_by(  
&nbsp;&nbsp;&nbsp;&nbsp;{ |x| x.department.name },  
&nbsp;&nbsp;&nbsp;&nbsp;{ |x| x.position.name },  
&nbsp;&nbsp;&nbsp;&nbsp;desc { |x| x.level },  
&nbsp;&nbsp;&nbsp;&nbsp;desc { |x| x.salary ? x.salary : x.rate \* db\_lookup(x,  
'hours\_worked') }  
&nbsp;&nbsp;)

I believe you could write a decent multi\_field\_sort\_by in Ruby that  
would be efficient for large enough lists to outperform more tedious  
approaches, but it would be even better if Ruby natively supported it.

My proposed syntax might be slightly off, but you get the idea. You'd  
pass a list of blocks that represent the successive tiebreakers, and  
multi\_field\_sort\_by would presumably cache the results from each  
transformation, evaluating the blocks only as necessary. The "desc"  
thingy would actually produce some kind of wrapper that  
multi\_field\_sort\_by could introspect to know that it needs to apply a  
particular tiebreaker in reverse order.

> **···**
>
> On Oct 13, 12:46 am, Rahul Kumar \<sentinel1...@gmail.com\> wrote:
> 
> > I need to use \*sort\_by\* to sort a table, since the user could select  
> > columns in any order.
> > 
> > ```
> > b=\[\[&quot;radio&quot;, 30, 5\], \[&quot;radio&quot;, 20, 5\], \[&quot;archie&quot;, 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.  
> > Thx, rahul.

---

<div class="post-metadata">

### Author: ![Steve\_Howell](https://avatars.discourse-cdn.com/v4/letter/s/b4bc9f/32.png) [@Steve\_Howell](https://rubytalk.org/u/Steve_Howell)
#### Post date: [14 October 2010 15:05 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/6 "2010-10-14T15:05:15Z")

</div>

Here is a solution that allows you to lazily evaluate keys and specify  
that certain keys are to be reversed.

&nbsp;&nbsp;module Enumerable  
&nbsp;&nbsp;&nbsp;&nbsp;def sort\_by(\*key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# allow for multiple key\_methods and only  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# evaluate them when they are truly needed  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# for the sort  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def compare(a,b, key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i = 0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while i \< key\_methods.size do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for elem in [a, b] do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fields = elem[0]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;key = elem[1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if fields.size \<= i  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fields[i] = key\_methods[i][0].call(key)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if key\_methods[i][1] == :desc  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a, b = b, a  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result = (a[0][i] \<=\> b[0][i])  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return result unless result == 0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i += 1  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return result  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.collect do |item|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[, item]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.sort do |a, b|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;compare(a, b, key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.collect do |kv|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;kv[1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end

&nbsp;&nbsp;a = [  
&nbsp;&nbsp;&nbsp;&nbsp;["radio", 30, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["radio", 40, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["radio", 20, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["archie", 20, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["newton", 10, 3]  
&nbsp;&nbsp;]

&nbsp;&nbsp;puts a.sort\_by(  
&nbsp;&nbsp;&nbsp;&nbsp;[Proc.new { |a| a[0] }],  
&nbsp;&nbsp;&nbsp;&nbsp;[Proc.new { |a| a[1] }, :desc]  
&nbsp;&nbsp;).inspect

> **···**
>
> On Oct 13, 12:46 am, Rahul Kumar \<sentinel1...@gmail.com\> wrote:
> 
> > I need to use \*sort\_by\* to sort a table, since the user could select  
> > columns in any order.
> > 
> > ```
> > b=\[\[&quot;radio&quot;, 30, 5\], \[&quot;radio&quot;, 20, 5\], \[&quot;archie&quot;, 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.

---

<div class="post-metadata">

### Author: ![R\_Kumar](https://avatars.discourse-cdn.com/v4/letter/r/c0e974/32.png) [@R\_Kumar](https://rubytalk.org/u/R_Kumar)
#### Post date: [13 October 2010 12:22 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/7 "2010-10-13T12:22:16Z")

</div>

Y. NOBUOKA wrote in post #949783:

> The one way to do a reverse sort is wrapping a sort target in a class  
> for a reverse sort.  
> For instance:

Thanks, i will have to consider this.

To the previous reply,

> Are you sure the one criterium version works as you think? !x[1] returns

a  
boolean value

Frankly, i am not sure. i just tried it on irb.

My issue is that i do not know how many columns a user will sort on, and  
what combination. So the normal sort() appears cumbersome, unless i can  
dynamically build a string and eval() it. Not sure i know how to do  
that.

sort\_by allows me to give a list as argument, so that appears suitable.

> **···**
>
> --  
> Posted via [http://www.ruby-forum.com/\](http://www.ruby-forum.com/%5C).

---

<div class="post-metadata">

### Author: ![Rob\_Biedenharn1](https://avatars.discourse-cdn.com/v4/letter/r/57b2e6/32.png) [@Rob\_Biedenharn1](https://rubytalk.org/u/Rob_Biedenharn1)
#### Post date: [13 October 2010 13:58 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/8 "2010-10-13T13:58:45Z")

</div>

> When i sat down to code, i realized that i could not just put the  
> sort\_keys list to sort\_by, i would have to unroll the sort\_keys array.  
> So, i might as well use sort. This seems to work in the small case i  
> have. Could someone comment on this, and suggest cleaner better way to  
> do. Thanks.
> 
> b=[["radio", 30, 5], ["radio", 20, 5], ["archie", 20, 5], ["newton",  
> 10, 3] ]  
> sort\_keys=[1,0]  
> rev\_flag = [true, false]
> 
> c=b.sort{|x,y|  
> res = 0  
> sort\_keys.each\_with\_index { |e,i|  
> &nbsp;&nbsp;&nbsp;if rev\_flag[i]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res = y[e] \<=\> x[e]  
> &nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res = x[e] \<=\> y[e]  
> &nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;break if res != 0  
> }  
> res  
> }  
> c
> 
> -- Posted via [http://www.ruby-forum.com/\](http://www.ruby-forum.com/%5C).

While it is not strictly equivalent to array\_of\_strings.sort.reverse, this little addition to String allows array\_of\_strings.sort\_by{|s|-s}

class String  
&nbsp;&nbsp;&nbsp;RAB\_REPLACE = ('a'..'z').to\_a.reverse.join.freeze  
&nbsp;&nbsp;&nbsp;def -@  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.tr('a-z',RAB\_REPLACE)  
&nbsp;&nbsp;&nbsp;end  
end

Then you can do:

b.sort\_by {|ary| [ary[1], -ary[0]] }

> b.sort\_by {|ary| [ary[1], -ary[0]] }

=\> [["newton", 10, 3], ["radio", 20, 5], ["archie", 20, 5], ["radio", 30, 5]]

If your strings are always simple words, then this may be enough for you (or at least give you an idea).

If your array is short (i.e., the difference between sort and sort\_by is acceptable), then you can always do:

> b.sort {|e1,e2| (e1[1] \<=\> e2[1]).nonzero? || e2[0] \<=\> e2[0] }

=\> [["newton", 10, 3], ["radio", 20, 5], ["archie", 20, 5], ["radio", 30, 5]]

( which doesn't rely on String#-@ )

-Rob

Rob Biedenharn   
Rob@AgileConsultingLLC.com [http://AgileConsultingLLC.com/](http://AgileConsultingLLC.com/)  
rab@GaslightSoftware.com [http://GaslightSoftware.com/](http://GaslightSoftware.com/)

> **···**
>
> On Oct 13, 2010, at 8:53 AM, Rahul Kumar wrote:

---

<div class="post-metadata">

### Author: ![Ryan\_Davis1](https://yyz1.discourse-cdn.com/flex029/user_avatar/rubytalk.org/ryan_davis1/32/1848_2.png) [@Ryan\_Davis1](https://rubytalk.org/u/Ryan_Davis1)
#### Post date: [14 October 2010 00:46 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/9 "2010-10-14T00:46:33Z")

</div>

I like this, but think it is more complex than it needs to be.

module ReverseSort  
&nbsp;&nbsp;def \<=\> target  
&nbsp;&nbsp;&nbsp;&nbsp;-super  
&nbsp;&nbsp;end  
end

class Object  
&nbsp;&nbsp;def -@  
&nbsp;&nbsp;&nbsp;&nbsp;self.dup.extend ReverseSort  
&nbsp;&nbsp;end  
end

b = [["radio", 30, 5], ["radio", 20, 5], ["archie", 20, 5], ["newton", 10, 3] ]

p b.sort\_by{|x| [x[1], -x[0] ]}  
# =\> [["newton", 10, 3], ["radio", 20, 5], ["archie", 20, 5], ["radio", 30, 5]]

> **···**
>
> On Oct 13, 2010, at 02:08 , Y. NOBUOKA wrote:
> 
> > The one way to do a reverse sort is wrapping a sort target in a class  
> > for a reverse sort.  
> > For instance:
> > 
> > # This class is a class for a reverse sort.  
> > # An Object of various datatypes can be wrapped in an object of this class.  
> > class ItemForReverseSort  
> > def initialize( item )  
> > &nbsp;&nbsp;&nbsp;@item = item  
> > end  
> > def item  
> > &nbsp;&nbsp;&nbsp;@item  
> > end  
> > def \<=\>( target )  
> > &nbsp;&nbsp;&nbsp;( self.item \<=\> target.item ) \* (-1)  
> > end  
> > end

---

<div class="post-metadata">

### Author: ![Steve\_Howell](https://avatars.discourse-cdn.com/v4/letter/s/b4bc9f/32.png) [@Steve\_Howell](https://rubytalk.org/u/Steve_Howell)
#### Post date: [14 October 2010 02:20 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/10 "2010-10-14T02:20:12Z")

</div>

I definitely feel that sort\_by has a compelling advantage over sort  
for any large-ish dataset involving keys with nontrivial  
transformations from the original object. You obviously want to avoid  
doing NlogN transformations when N would suffice.

Does Ruby's sort\_by method guarantee a stable sort?

If it does, then you could do sort\_by the first field (reversing as  
needed), then sort\_by the second field (reversing as needed), then  
sort\_by the third field (reversing as needed), and so on. Doing M  
sort passes on 1 key is no less efficient than doing 1 sort pass on M  
keys, except to the extent that the latter maybe short-circuits a few  
comparisons. At worst you are talking an M-ish difference that is  
mostly dwarfed by NlogN.

Your essential underlying question about representing the "inverse" of  
a key is pretty intriguing. I don't know of any general way to solve  
that problem, other than encapsulating it in a class, as has been  
suggested.

> **···**
>
> On Oct 13, 5:53 am, Rahul Kumar \<sentinel1...@gmail.com\> wrote:
> 
> > When i sat down to code, i realized that i could not just put the  
> > sort\_keys list to sort\_by, i would have to unroll the sort\_keys array.  
> > So, i might as well use sort. This seems to work in the small case i  
> > have. Could someone comment on this, and suggest cleaner better way to  
> > do. Thanks.
> > 
> > b=[["radio", 30, 5], ["radio", 20, 5], ["archie", 20, 5], ["newton",  
> > 10, 3] ]  
> > sort\_keys=[1,0]  
> > rev\_flag = [true, false]
> > 
> > c=b.sort{|x,y|  
> > res = 0  
> > sort\_keys.each\_with\_index { |e,i|  
> > if rev\_flag[i]  
> > res = y[e] \<=\> x[e]  
> > else  
> > res = x[e] \<=\> y[e]  
> > end  
> > break if res != 0  
> > }  
> > res}

---

<div class="post-metadata">

### Author: ![Jeremy\_Bopp](https://yyz1.discourse-cdn.com/flex029/user_avatar/rubytalk.org/jeremy_bopp/32/1937_2.png) [@Jeremy\_Bopp](https://rubytalk.org/u/Jeremy_Bopp)
#### Post date: [14 October 2010 05:50 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/11 "2010-10-14T05:50:49Z")

</div>

How about something like this:

module Enumerable  
&nbsp;&nbsp;# sort\_by will take a key generator and an optional comparator  
&nbsp;&nbsp;# and perform a Schwartzian Transform over the data.

> **···**
>
> On 10/13/2010 09:50 PM, Steve Howell wrote:
> 
> > It would be nice if Ruby supported a sort\_by on steroids.
> > 
> > &nbsp;&nbsp;sorted\_list = list.multi\_field\_sort\_by(  
> > &nbsp;&nbsp;&nbsp;&nbsp;{ |x| x.department.name },  
> > &nbsp;&nbsp;&nbsp;&nbsp;{ |x| x.position.name },  
> > &nbsp;&nbsp;&nbsp;&nbsp;desc { |x| x.level },  
> > &nbsp;&nbsp;&nbsp;&nbsp;desc { |x| x.salary ? x.salary : x.rate \* db\_lookup(x,  
> > 'hours\_worked') }  
> > &nbsp;&nbsp;)
> > 
> > I believe you could write a decent multi\_field\_sort\_by in Ruby that  
> > would be efficient for large enough lists to outperform more tedious  
> > approaches, but it would be even better if Ruby natively supported it.
> > 
> > My proposed syntax might be slightly off, but you get the idea. You'd  
> > pass a list of blocks that represent the successive tiebreakers, and  
> > multi\_field\_sort\_by would presumably cache the results from each  
> > transformation, evaluating the blocks only as necessary. The "desc"  
> > thingy would actually produce some kind of wrapper that  
> > multi\_field\_sort\_by could introspect to know that it needs to apply a  
> > particular tiebreaker in reverse order.
> 
> &nbsp;&nbsp;#  
> &nbsp;&nbsp;# This is a general solution which is relatively inefficient than  
> &nbsp;&nbsp;# a purpose-built sorting function if the keys are trivial to  
> &nbsp;&nbsp;# generate.  
> &nbsp;&nbsp;def sort\_by(cmp = lambda { |a, b| a \<=\> b }, &key)  
> &nbsp;&nbsp;&nbsp;&nbsp;collect do |item| # Generate keys from the list items.  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[key[item], item]  
> &nbsp;&nbsp;&nbsp;&nbsp;end.sort do |a, b| # Sort the keys.  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cmp[a[0], b[0]]  
> &nbsp;&nbsp;&nbsp;&nbsp;end.collect do |kv| # Return the items in key sort order.  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;kv[1]  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end  
> end
> 
> # This will cause the sort to operate over the second and then  
> # the first column.  
> key = lambda { |item| [item[1], item[0]] }
> 
> # This will cause the sort to operate normally on the second  
> # column and in reverse on the first column.  
> cmp = lambda do |a, b|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res = a[0] \<=\> b[0]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break res unless res == 0  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b[1] \<=\> a[1]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> # Sample data from Ryan's post.  
> a = [  
> &nbsp;&nbsp;["radio", 30, 5],  
> &nbsp;&nbsp;["radio", 20, 5],  
> &nbsp;&nbsp;["archie", 20, 5],  
> &nbsp;&nbsp;["newton", 10, 3]  
> ]
> 
> # Sort over the second and then first columns each in normal order.  
> p a.sort\_by(&key)  
> # =\> [["newton", 10, 3], ["archie", 20, 5], ["radio", 20, 5],  
> # ["radio", 30, 5]]
> 
> # Sort over the second column in normal order and then the first  
> # column in reverse order.  
> p a.sort\_by(cmp, &key)  
> # =\> [["newton", 10, 3], ["radio", 20, 5], ["archie", 20, 5],  
> # ["radio", 30, 5]]

---

<div class="post-metadata">

### Author: ![Steve\_Howell](https://avatars.discourse-cdn.com/v4/letter/s/b4bc9f/32.png) [@Steve\_Howell](https://rubytalk.org/u/Steve_Howell)
#### Post date: [14 October 2010 15:35 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/12 "2010-10-14T15:35:12Z")

</div>

Oops, I forgot to verify that I was actually caching results. Here is  
a fixed version.

&nbsp;&nbsp;module Enumerable  
&nbsp;&nbsp;&nbsp;&nbsp;def sort\_by\_multi(\*key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# allow for multiple key\_methods and only  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# evaluate them when they are truly needed  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# for the sort  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def compare(a,b, key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i = 0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while i \< key\_methods.size do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for elem in [a, b] do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;key = elem[1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if elem[0].size \<= i  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elem[0] \<\< key\_methods[i][0].call(key)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts " #{elem.inspect}"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if key\_methods[i][1] == :desc  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a, b = b, a  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result = (a[0][i] \<=\> b[0][i])  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return result unless result == 0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i += 1  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return result  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.collect do |item|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[, item]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.sort do |a, b|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;compare(a, b, key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.collect do |kv|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;kv[1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end

&nbsp;&nbsp;a = [  
&nbsp;&nbsp;&nbsp;&nbsp;["radio", 30, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["radio", 40, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["radio", 20, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["archie", 20, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["newton", 10, 3]  
&nbsp;&nbsp;]

&nbsp;&nbsp;puts a.sort\_by\_multi(  
&nbsp;&nbsp;&nbsp;&nbsp;[Proc.new { |a| a[0] }],  
&nbsp;&nbsp;&nbsp;&nbsp;[Proc.new { |a| a[1] }, :desc]  
&nbsp;&nbsp;).inspect

You can see that it only evaluates the second key for tiebreaker  
purposes. (Of course, in this example, the key calculation is  
trivial, but in the real world you might have a key that is expensive  
to evaluate.)

&nbsp;&nbsp;[["radio"], ["radio", 30, 5]]  
&nbsp;&nbsp;[["radio"], ["radio", 20, 5]]  
&nbsp;&nbsp;[["radio", 30], ["radio", 30, 5]]  
&nbsp;&nbsp;[["radio", 20], ["radio", 20, 5]]  
&nbsp;&nbsp;[["newton"], ["newton", 10, 3]]  
&nbsp;&nbsp;[["radio"], ["radio", 40, 5]]  
&nbsp;&nbsp;[["radio", 40], ["radio", 40, 5]]  
&nbsp;&nbsp;[["archie"], ["archie", 20, 5]]  
[["archie", 20, 5], ["newton", 10, 3], ["radio", 40, 5], ["radio", 30,  
5], ["radio", 20, 5]]

> **···**
>
> On Oct 14, 8:00 am, Steve Howell \<showel...@yahoo.com\> wrote:
> 
> > On Oct 13, 12:46 am, Rahul Kumar \<sentinel1...@gmail.com\> 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

---

<div class="post-metadata">

### Author: ![Steve\_Howell](https://avatars.discourse-cdn.com/v4/letter/s/b4bc9f/32.png) [@Steve\_Howell](https://rubytalk.org/u/Steve_Howell)
#### Post date: [14 October 2010 14:06 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/13 "2010-10-14T14:06:38Z")

</div>

I think it's a good start, but it still requires you to write a  
somewhat customized cmp function. A truly optimized multi-field sort  
might also be able to avoid calculating all elements of the key as  
well, since they are often only needed as tiebreakers.

> **···**
>
> On Oct 13, 10:50 pm, Jeremy Bopp \<jer...@bopp.net\> wrote:
> 
> > On 10/13/2010 09:50 PM, Steve Howell wrote:
> > 
> > \> It would be nice if Ruby supported a sort\_by on steroids.
> > 
> > \> sorted\_list = list.multi\_field\_sort\_by(  
> > \> { |x| x.department.name },  
> > \> { |x| x.position.name },  
> > \> desc { |x| x.level },  
> > \> desc { |x| x.salary ? x.salary : x.rate \* db\_lookup(x,  
> > \> 'hours\_worked') }  
> > \> )
> > 
> > \> I believe you could write a decent multi\_field\_sort\_by in Ruby that  
> > \> would be efficient for large enough lists to outperform more tedious  
> > \> approaches, but it would be even better if Ruby natively supported it.
> > 
> > \> My proposed syntax might be slightly off, but you get the idea. You'd  
> > \> pass a list of blocks that represent the successive tiebreakers, and  
> > \> multi\_field\_sort\_by would presumably cache the results from each  
> > \> transformation, evaluating the blocks only as necessary. The "desc"  
> > \> thingy would actually produce some kind of wrapper that  
> > \> multi\_field\_sort\_by could introspect to know that it needs to apply a  
> > \> particular tiebreaker in reverse order.
> > 
> > How about something like this:
> > 
> > module Enumerable  
> > # sort\_by will take a key generator and an optional comparator  
> > # and perform a Schwartzian Transform over the data.  
> > #  
> > # This is a general solution which is relatively inefficient than  
> > # a purpose-built sorting function if the keys are trivial to  
> > # generate.  
> > def sort\_by(cmp = lambda { |a, b| a \<=\> b }, &key)  
> > collect do |item| # Generate keys from the list items.  
> > [key[item], item]  
> > end.sort do |a, b| # Sort the keys.  
> > cmp[a[0], b[0]]  
> > end.collect do |kv| # Return the items in key sort order.  
> > kv[1]  
> > end  
> > end  
> > end
> > 
> > # This will cause the sort to operate over the second and then  
> > # the first column.  
> > key = lambda { |item| [item[1], item[0]] }
> > 
> > # This will cause the sort to operate normally on the second  
> > # column and in reverse on the first column.  
> > cmp = lambda do |a, b|  
> > res = a[0] \<=\> b[0]  
> > break res unless res == 0  
> > b[1] \<=\> a[1]  
> > end
> > 
> > # Sample data from Ryan's post.  
> > a = [  
> > ["radio", 30, 5],  
> > ["radio", 20, 5],  
> > ["archie", 20, 5],  
> > ["newton", 10, 3]  
> > ]
> > 
> > # Sort over the second and then first columns each in normal order.  
> > p a.sort\_by(&key)  
> > # =\> [["newton", 10, 3], ["archie", 20, 5], ["radio", 20, 5],  
> > # ["radio", 30, 5]]
> > 
> > # Sort over the second column in normal order and then the first  
> > # column in reverse order.  
> > p a.sort\_by(cmp, &key)  
> > # =\> [["newton", 10, 3], ["radio", 20, 5], ["archie", 20, 5],  
> > # ["radio", 30, 5]]

---

<div class="post-metadata">

### Author: ![Ryan\_Davis1](https://yyz1.discourse-cdn.com/flex029/user_avatar/rubytalk.org/ryan_davis1/32/1848_2.png) [@Ryan\_Davis1](https://rubytalk.org/u/Ryan_Davis1)
#### Post date: [14 October 2010 20:25 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/14 "2010-10-14T20:25:23Z")

</div>

This doesn't do what you think (or at least imply that) it does.

> **···**
>
> On Oct 14, 2010, at 08:35 , Steve Howell wrote:
> 
> > module Enumerable  
> > &nbsp;&nbsp;&nbsp;def sort\_by\_multi(\*key\_methods)  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def compare(a,b, key\_methods)

---

<div class="post-metadata">

### Author: ![Steve\_Howell](https://avatars.discourse-cdn.com/v4/letter/s/b4bc9f/32.png) [@Steve\_Howell](https://rubytalk.org/u/Steve_Howell)
#### Post date: [15 October 2010 01:10 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/15 "2010-10-15T01:10:19Z")

</div>

Care to explain?

&nbsp;&nbsp;puts a.sort\_by\_multi(  
&nbsp;&nbsp;&nbsp;&nbsp;[Proc.new { |a| a[0] }],  
&nbsp;&nbsp;&nbsp;&nbsp;[Proc.new { |a| a[1] }, :desc]  
&nbsp;&nbsp;).inspect

$ ruby foo.rb

&nbsp;&nbsp;[["archie", 20, 5], ["newton", 10, 3], ["radio", 40, 5], ["radio",  
30, 5], ["radio", 20, 5]]

$ cat foo.rb  
&nbsp;&nbsp;module Enumerable  
&nbsp;&nbsp;&nbsp;&nbsp;def sort\_by\_multi(\*key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# allow for multiple key\_methods and only  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# evaluate them when they are truly needed  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# for the sort  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def compare(a,b, key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i = 0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while i \< key\_methods.size do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for elem in [a, b] do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;key = elem[1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if elem[0].size \<= i  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elem[0] \<\< key\_methods[i][0].call(key)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if key\_methods[i][1] == :desc  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a, b = b, a  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result = (a[0][i] \<=\> b[0][i])  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return result unless result == 0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i += 1  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return result  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.collect do |item|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[, item]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.sort do |a, b|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;compare(a, b, key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.collect do |kv|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;kv[1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end

&nbsp;&nbsp;a = [  
&nbsp;&nbsp;&nbsp;&nbsp;["radio", 30, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["radio", 40, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["radio", 20, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["archie", 20, 5],  
&nbsp;&nbsp;&nbsp;&nbsp;["newton", 10, 3]  
&nbsp;&nbsp;]

&nbsp;&nbsp;puts a.sort\_by\_multi(  
&nbsp;&nbsp;&nbsp;&nbsp;[Proc.new { |a| a[0] }],  
&nbsp;&nbsp;&nbsp;&nbsp;[Proc.new { |a| a[1] }, :desc]  
&nbsp;&nbsp;).inspect

> **···**
>
> On Oct 14, 1:25 pm, Ryan Davis \<ryand-r...@zenspider.com\> wrote:
> 
> > On Oct 14, 2010, at 08:35 , Steve Howell wrote:
> > 
> > \> module Enumerable  
> > \> def sort\_by\_multi(\*key\_methods)  
> > \> def compare(a,b, key\_methods)
> > 
> > This doesn't do what you think (or at least imply that) it does.

---

<div class="post-metadata">

### Author: ![Steve\_Howell](https://avatars.discourse-cdn.com/v4/letter/s/b4bc9f/32.png) [@Steve\_Howell](https://rubytalk.org/u/Steve_Howell)
#### Post date: [15 October 2010 05:40 UTC](https://rubytalk.org/t/sort-by-multiple-fields-with-reverse-sort/60423/16 "2010-10-15T05:40:24Z")

</div>

Ok, I found and fixed a bug (\*) and reworked the example to  
(hopefully) make the implied intent more clear.

&nbsp;&nbsp;rows = [  
&nbsp;&nbsp;&nbsp;&nbsp;{:name =\> "al", :salary =\> 40},  
&nbsp;&nbsp;&nbsp;&nbsp;{:name =\> 'charlie', :salary =\> 100},  
&nbsp;&nbsp;&nbsp;&nbsp;{:name =\> "al", :salary =\> 50},  
&nbsp;&nbsp;&nbsp;&nbsp;{:name =\> 'diane', :salary =\> 1000, :commission =\> 40},  
&nbsp;&nbsp;&nbsp;&nbsp;{:name =\> 'diane', :salary =\> 1000, :commission =\> 30},  
&nbsp;&nbsp;&nbsp;&nbsp;{:name =\> 'ed', :salary =\> 20},  
&nbsp;&nbsp;]

&nbsp;&nbsp;def do\_sql\_like\_sort(rows)  
&nbsp;&nbsp;&nbsp;&nbsp;def asc(field)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Proc.new { |row| row[field] }, :asc ]  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;def desc(field)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Proc.new { |row| row[field] }, :desc ]  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;# The semantics for sort\_by\_multi are similar to  
&nbsp;&nbsp;&nbsp;&nbsp;# SQL.  
&nbsp;&nbsp;&nbsp;&nbsp;rows.sort\_by\_multi(  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;asc(:name),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;desc(:salary),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;desc(:commission)  
&nbsp;&nbsp;&nbsp;&nbsp;)  
&nbsp;&nbsp;end

&nbsp;&nbsp;expected\_result = [  
&nbsp;&nbsp;&nbsp;&nbsp;{:name=\>"al", :salary=\>50},  
&nbsp;&nbsp;&nbsp;&nbsp;{:name=\>"al", :salary=\>40},  
&nbsp;&nbsp;&nbsp;&nbsp;{:name=\>"charlie", :salary=\>100},  
&nbsp;&nbsp;&nbsp;&nbsp;{:name=\>"diane", :salary=\>1000, :commission =\> 40},  
&nbsp;&nbsp;&nbsp;&nbsp;{:name=\>"diane", :salary=\>1000, :commission =\> 30},  
&nbsp;&nbsp;&nbsp;&nbsp;{:name=\>"ed", :salary=\>20},  
&nbsp;&nbsp;]

&nbsp;&nbsp;module Enumerable  
&nbsp;&nbsp;&nbsp;&nbsp;def sort\_by\_multi(\*key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# allow for multiple key\_methods and only  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# evaluate them when they are truly needed  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# for the sort  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def compare(a,b, key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i = 0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while i \< key\_methods.size do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for elem in [a, b] do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;key = elem[1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if elem[0].size \<= i  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elem[0] \<\< key\_methods[i][0].call(key)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x, y = (key\_methods[i][1] == :desc) ? [b, a] : [a, b]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result = (x[0][i] \<=\> y[0][i])  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return result unless result == 0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i += 1  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return result  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.collect do |item|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[, item]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.sort do |a, b|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;compare(a, b, key\_methods)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.collect do |kv|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;kv[1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end

&nbsp;&nbsp;sorted\_rows = do\_sql\_like\_sort(rows)  
&nbsp;&nbsp;if sorted\_rows != expected\_result  
&nbsp;&nbsp;&nbsp;&nbsp;raise 'fail'  
&nbsp;&nbsp;end

\* - The previous iteration of this code was swapping a and b instead  
of making copies before swapping. This broke the case where two of  
the fields were to be sorted in descending order.

> **···**
>
> On Oct 14, 1:25 pm, Ryan Davis \<ryand-r...@zenspider.com\> wrote:
> 
> > On Oct 14, 2010, at 08:35 , Steve Howell wrote:
> > 
> > \> module Enumerable  
> > \> def sort\_by\_multi(\*key\_methods)  
> > \> def compare(a,b, key\_methods)
> > 
> > This doesn't do what you think (or at least imply that) it does.
