[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

how to vary sort's block?

Wybo Dekker

2/4/2006 10:40:00 AM

7 Answers

Robert Klemme

2/4/2006 12:48:00 PM

0

Wybo Dekker <wybo@servalys.nl> 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').
>
> 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

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

Ross Bamford

2/4/2006 1:16:00 PM

0

On Sat, 2006-02-04 at 19:40 +0900, Wybo Dekker 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').
>
> 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?
>

Yes, see below:

> #!/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)
> ]

# 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-t... )

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk



Wybo Dekker

2/4/2006 2:45:00 PM

0

Robert Klemme

2/4/2006 5:16:00 PM

0


"Wybo Dekker" <wybo@servalys.nl> schrieb im Newsbeitrag
news:Pine.LNX.4.61.0602041523170.10213@servalys.nl...
> On Sat, 4 Feb 2006, Robert Klemme 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?

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).

> 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]

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


James Gray

2/4/2006 5:26:00 PM

0

On Feb 4, 2006, at 4:40 AM, Wybo Dekker 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 Gray II



Wybo Dekker

2/5/2006 1:17:00 PM

0

Mark Wilden

2/6/2006 12:46:00 AM

0

"Wybo Dekker" <wybo@servalys.nl> wrote in message
news:Pine.LNX.4.61.0602051338380.10213@servalys.nl...

> 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.