# How to vary sort's block?

**URL:** <https://www.ruby-forum.com/t/how-to-vary-sorts-block/53413>\
**Category:** Ruby\
**Created:** [February 4, 2006, 11:40am UTC](https://www.ruby-forum.com/t/how-to-vary-sorts-block/53413 "2006-02-04T11:40:49Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![wybodekker](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/w/f05b48/32.png) [@wybodekker](https://www.ruby-forum.com/u/wybodekker)\
**Post date:** [February 4, 2006, 11:40am UTC](https://www.ruby-forum.com/t/how-to-vary-sorts-block/53413/1 "2006-02-04T11:40:49Z")

</div>

When I have an array of objects (Thing’s, say) with two properties (say  
`name' and`amount’) I want to be able to sort that array by either of  
these properties, depending on circumstances (like the value of the  
variable `order’).

The script below is an example, and it works, but I don’t like it.  
I there a better way to do it, without an if…else construction?

For example, would it be possible to put several {|a,b|…} blocks for  
the sort in a hash with keys telling what I want to sort on?

#!/usr/bin/ruby

class Thing  
attr\_reader :name,:amount

def initialize(name,amount)  
@name,@amount = name,amount  
end

def list  
puts [’’,@name,@amount].join("\t")  
end  
end

class Array  
def byamount  
self.sort { |a,b| a.amount \<=\> b.amount }  
end  
def byname  
self.sort { |a,b| a.name \<=\> b.name }  
end  
end

arr = [  
Thing.new(‘John’,10),  
Thing.new(‘Anny’,20)  
]

order = :byname

puts “sorted #{order}:”  
if order == :byname  
arr.sort { |a,b| a.name \<=\> b.name }.each { |a|  
a.list  
# much more code may occur here…  
}  
elsif order == :byamount  
arr.sort { |a,b| a.amount \<=\> b.amount }.each { |a|  
a.list  
# much more code may occur here…  
}  
else  
raise “illegal order”  
end

---

<div class="post-metadata">

**Author:** ![wybodekker](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/w/f05b48/32.png) [@wybodekker](https://www.ruby-forum.com/u/wybodekker)\
**Post date:** [February 4, 2006, 1:55pm UTC](https://www.ruby-forum.com/t/how-to-vary-sorts-block/53413/2 "2006-02-04T13:55:48Z")

</div>

Wybo D. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> puts [‘’,@name,@amount].join(“\t”)  
> end  
> arr.sort { |a,b| a.name \<=\> b.name }.each { |a|  
> end  
> The easiest is probably to use sort\_by.

Thing = Struct.new(:name, :amount)  
arr = [  
Thing.new(‘John’,10),  
Thing.new(‘Anny’,20),  
]

p arr.sort\_by {|x| x.name}  
p arr.sort\_by {|x| x.amount}

Btw, I would _not_ put byamount and byname into Array. If you want to  
do  
something general, then I’d do this:

module Enumerable  
def sort\_field(\*fields)  
sort\_by {|x| fields.map {|f| x.send f}}  
end  
end

Then you can do

p arr.sort\_field :name  
p arr.sort\_field :amount

It might even be reasonable to replace the current implementation of  
sort\_by  
with one that accepts either a list of symbols as arguments or a block.  
I  
think this might have been proposed already. Matz, any comment on this?  
Does this sound reasonable?

Kind regards

```
robert

```

---

<div class="post-metadata">

**Author:** ![wybodekker](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/w/f05b48/32.png) [@wybodekker](https://www.ruby-forum.com/u/wybodekker)\
**Post date:** [February 4, 2006, 3:48pm UTC](https://www.ruby-forum.com/t/how-to-vary-sorts-block/53413/3 "2006-02-04T15:48:19Z")

</div>

On Sat, 4 Feb 2006, Robert K. wrote:

> The easiest is probably to use sort\_by.
> 
> Thing = Struct.new(:name, :amount)  
> arr = [  
> Thing.new(‘John’,10),  
> Thing.new(‘Anny’,20),  
> ]
> 
> p arr.sort\_by {|x| x.name}  
> p arr.sort\_by {|x| x.amount}

this does not allow for more complex sorts, like on name _and_ amount,  
does it?

> p arr.sort\_field :name  
> p arr.sort\_field :amount

but this is really great, I can now even say:  
p arr.sort\_field(:name,:amount)  
Thanks!

> It might even be reasonable to replace the current implementation of sort\_by  
> with one that accepts either a list of symbols as arguments or a block. I  
> think this might have been proposed already. Matz, any comment on this? Does  
> this sound reasonable?

I saw that it is also possible to give sort an argument instead of a  
block:

require ‘pp’

Thing = Struct.new(:name, :amount)  
arr = [  
Thing.new(‘John’,80),  
Thing.new(‘John’,10),  
Thing.new(‘John’,30),  
Thing.new(‘Anny’,20),  
Thing.new(‘Anny’,10),  
Thing.new(‘Anny’,30),  
Thing.new(‘Anny’,15)  
]

def byname(a,b) a.name \<=\> b.name end  
def byamount(a,b) a.amount \<=\> b.amount end  
def by\_name\_amount(a,b)  
(a.name \<=\> b.name)\*4 + (a.amount \<=\> b.amount)  
end

[:byname,:byamount,:by\_name\_amount].each do |order|  
puts “sort #{order}:”  
pp arr.sort(&method(order))  
end

But ri does not tell me that:

## -------------------------------------------------------- Enumerable#sort enum.sort =\> array enum.sort {| a, b | block } =\> array

```
 Returns an array containing the items in _enum_ sorted, either
 according to their own +<=>+ method, or by using the results of the
 supplied block. The block should return -1, 0, or +1 depending on
 the comparison between _a_ and _b_. As of Ruby 1.8, the method
 +Enumerable#sort_by+ implements a built-in Schwartzian Transform,
 useful when key computation or comparison is expensive..

    %w(rhea kea flea).sort #=> ["flea", "kea", "rhea"]
    (1..10).sort {|a,b| b <=> a} #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 

```

1]

---

<div class="post-metadata">

**Author:** ![wybodekker](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/w/f05b48/32.png) [@wybodekker](https://www.ruby-forum.com/u/wybodekker)\
**Post date:** [February 4, 2006, 2:17pm UTC](https://www.ruby-forum.com/t/how-to-vary-sorts-block/53413/4 "2006-02-04T14:17:25Z")

</div>

On Sat, 2006-02-04 at 19:40 +0900, Wybo D. wrote:

> 

Yes, see below:

> ```
> puts ['',@name,@amount].join("\t")
> 
> ```
> 
> end
> 
> arr = [  
> Thing.new(‘John’,10),  
> Thing.new(‘Anny’,20)  
> ]

# Replace rest of the code with:

orders = {  
:byname =\> lambda { |a,b| a.name \<=\> b.name },  
:byamount =\> lambda { |a,b| a.amount \<=\> b.amount }  
}

order = :byname

if blk = orders[order]  
puts “sorted #{order}:”  
arr.sort(&blk).each { |a| a.list }  
else  
raise “illegal order”  
end

**END**

This would be my first implementation I think, I like dispatch table  
type stuff. Be careful what you enclose with all those blocks, though.

An alternative idea might be (replacing the code I show above):

```
require 'curry' # Have to download this, see bottom

sort_proc = lambda { |msg,a,b| a.send(msg) <=> b.send(msg) }

by_name = sort_proc.curry(:name)
by_amount = sort_proc.curry(:amount)

puts "sorted name:"
arr.sort(&by_name).each { |a| a.list }

```

> > # =\> sorted name:
> > 
> > # =\> Anny 20
> > 
> > # =\> John 10

```
puts "sorted amount:"
arr.sort(&by_amount).each { |a| a.list }

```

> > # =\> sorted amount:
> > 
> > # =\> John 10
> > 
> > # =\> Anny 20

(You could curry on the fly of course depending on the sort criteria,  
it’s reasonably low-overhead).

Does it show I’ve been searching for an opportunity to use currying  
since I did that quiz entry? ;D

(Curry: [http://www.ruby-talk.org/cgi-bin/scat.rb/ruby/ruby-talk/177343](http://www.ruby-talk.org/cgi-bin/scat.rb/ruby/ruby-talk/177343) )

---

<div class="post-metadata">

**Author:** ![wybodekker](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/w/f05b48/32.png) [@wybodekker](https://www.ruby-forum.com/u/wybodekker)\
**Post date:** [February 4, 2006, 6:21pm UTC](https://www.ruby-forum.com/t/how-to-vary-sorts-block/53413/5 "2006-02-04T18:21:01Z")

</div>

“Wybo D.” [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) schrieb im Newsbeitrag  
news:removed\_email\_address@domain.invalid…

> > p arr.sort\_by {|x| x.name}  
> > p arr.sort\_by {|x| x.amount}
> 
> this does not allow for more complex sorts, like on name _and_ amount,  
> does it?

It does: arr.sort\_by {|x| [x.name, x.amount]} (depending on the  
precedence  
you want). I believe, if you use Struct and want to order according to  
field order then you might be able to just use sort() without arguments  
(alternatively sort\_by {|x| x.to\_a} - to\_a is defined by Struct).

> Thing.new(‘Anny’,20),
> 
> * * *
> 
> ```
> Returns an array containing the items in _enum_ sorted, either
> according to their own +<=>+ method, or by using the results of the
> supplied block. The block should return -1, 0, or +1 depending on
> the comparison between _a_ and _b_. As of Ruby 1.8, the method
> +Enumerable#sort_by+ implements a built-in Schwartzian Transform,
> useful when key computation or comparison is expensive..
> 
> %w(rhea kea flea).sort #=> ["flea", "kea", "rhea"]
> (1..10).sort {|a,b| b <=> a} #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
> 
> ```

Actually ri tells you that - because it’s the block form. Your methods  
are  
implicitely converted to blocks. In that case I’d rather not use a  
method  
definition but rather do the more direct lambda / proc:

Thing::BYNAME = lambda {|a,b| a.name \<=\> b.name}  
Thing::BYAMOUNT = lambda {|a,b| a.amount \<=\> b.amount}  
…  
arr.sort(&Thing::BYNAME)

You can as well define those constants in global scope but I felt they  
are  
more appropriately placed in Thing’s scope because they refer exactly to  
Things.

Kind regards

```
robert

```

---

<div class="post-metadata">

**Author:** ![wybodekker](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/w/f05b48/32.png) [@wybodekker](https://www.ruby-forum.com/u/wybodekker)\
**Post date:** [February 4, 2006, 6:27pm UTC](https://www.ruby-forum.com/t/how-to-vary-sorts-block/53413/6 "2006-02-04T18:27:08Z")

</div>

On Feb 4, 2006, at 4:40 AM, Wybo D. wrote:

> When I have an array of objects (Thing’s, say) with two properties  
> (say  
> `name' and`amount’) I want to be able to sort that array by either of  
> these properties, depending on circumstances (like the value of the  
> variable `order’).

I would use send():

#!/usr/bin/ruby

class Thing  
attr\_reader :name,:amount

def initialize(name,amount)  
@name,@amount = name,amount  
end

def list  
puts [’’,@name,@amount].join("\t")  
end  
end

arr = [  
Thing.new(‘John’,10),  
Thing.new(‘Anny’,20)  
]

order = :name

puts “sorted by #{order}:”  
arr.sort\_by { |thing| thing.send(order) }.each { |a|  
a.list  
# much more code may occur here…  
} rescue puts(“illegal order”)

**END**

Hope that helps.

James Edward G. II

---

<div class="post-metadata">

**Author:** ![wybodekker](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/w/f05b48/32.png) [@wybodekker](https://www.ruby-forum.com/u/wybodekker)\
**Post date:** [February 5, 2006, 2:19pm UTC](https://www.ruby-forum.com/t/how-to-vary-sorts-block/53413/7 "2006-02-05T14:19:29Z")

</div>

Thanks all for the very valuable explanations and suggestions.  
I finally came to the conclusion that, for my problem (to do a sort,  
depending on the value of some variable `order’), sort\_by is the most  
versatile way. I saved an example for future reference, which may be  
useful for others on the list:

#!/usr/bin/ruby

class Thing  
attr\_reader :name,:amount  
def initialize(name,amount)  
@name,@amount = name,amount  
end  
def list  
puts [’’,@name,@amount].join("\t")  
end  
end

arr = [  
Thing.new(‘Johnny’,20), Thing.new(‘Anny’,20),  
Thing.new(‘John’,10), Thing.new(‘Anny’,10),  
Thing.new(‘John’,30), Thing.new(‘Anny’,30),  
]

for order in [:name, :amount]  
puts “by #{order}:”  
arr.sort\_by{|x| x.send(order)}.each {|a| a.list}  
end

order = :name  
puts “by #{order}\_length, #{order}, amount:”  
arr.sort\_by{|x| [ x.send(order).length,  
x.send(order),  
x.amount  
]  
}.each {|a| a.list}

PS. Good to see in the definition of arr that the extra comma after the  
last element is not an error anymore.

---

<div class="post-metadata">

**Author:** ![wybodekker](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/w/f05b48/32.png) [@wybodekker](https://www.ruby-forum.com/u/wybodekker)\
**Post date:** [February 6, 2006, 1:51am UTC](https://www.ruby-forum.com/t/how-to-vary-sorts-block/53413/8 "2006-02-06T01:51:13Z")

</div>

“Wybo D.” [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote in message  
news:removed\_email\_address@domain.invalid…

> PS. Good to see in the definition of arr that the extra comma after the  
> last element is not an error anymore.

Agreed. C# is the same.
