Sort array by two attributes? (like sql "order by A, B")

M

Max Williams

IN sql we can pass two arguments to the 'order by' component, and it
will order the results by A, and then B in the cases where A is the
same.

Can anyone think of a way to do the same thing with a ruby array? For
example, if the array holds objects that have attributes/methods
"lastname" & "firstname", to order the objects in a similar way to the
sql query?

thanks
max
 
J

James Coglan

[Note: parts of this message were removed to make it a legal post.]
Can anyone think of a way to do the same thing with a ruby array? For
example, if the array holds objects that have attributes/methods
"lastname" & "firstname", to order the objects in a similar way to the
sql query?



Just use Enumerable#sort:
http://ruby-doc.org/core/classes/Enumerable.html#M003150

objects.sort do |a,b|
comp = (a.lastname <=> b.lastname)
comp.zero? ? (a.firstname <=> b.firstname) : comp
end

James
http://blog.jcoglan.com
http://github.com/jcoglan
 
J

Jesús Gabriel y Galán

IN sql we can pass two arguments to the 'order by' component, and it
will order the results by A, and then B in the cases where A is the
same.

Can anyone think of a way to do the same thing with a ruby array? For
example, if the array holds objects that have attributes/methods
"lastname" & "firstname", to order the objects in a similar way to the
sql query?

Here's one way: the trick is to create an array with the fields you want
to order by in the sort_by block:

irb(main):001:0> class A
irb(main):002:1> attr_accessor :a,:b
irb(main):003:1> def initialize a,b
irb(main):004:2> @a = a
irb(main):005:2> @b = b
irb(main):006:2> end
irb(main):007:1> end
=> nil
irb(main):008:0> ary = [A.new(1,2), A.new(1,3), A.new(1,1),
A.new(2,3), A.new(2,1)]
=> [#<A:0xb7b68890 @b=2, @a=1>, #<A:0xb7b6887c @b=3, @a=1>,
#<A:0xb7b68868 @b=1, @a=1>, #<A:0xb7b68854 @b=3, @a=2>, #<A:0xb7b68840
@b=1, @a=2>]
irb(main):009:0> ary.sort_by {|x| [x.a,x.b]}
=> [#<A:0xb7b68868 @b=1, @a=1>, #<A:0xb7b68890 @b=2, @a=1>,
#<A:0xb7b6887c @b=3, @a=1>, #<A:0xb7b68840 @b=1, @a=2>, #<A:0xb7b68854
@b=3, @a=2>]

Hope this helps,

Jesus.
 
M

Max Williams

Jesús Gabriel y Galán said:
Here's one way: the trick is to create an array with the fields you want
to order by in the sort_by block:
Hope this helps,

Jesus.

That's a neat trick, thanks! I actually like the first one better
though as you can swap a & b around in each test to get (eg) sorted by
name ascending and then a date field descending.

Thanks a lot guys!
max
 
E

Erik Veenstra

That's a neat trick, thanks! =A0I actually like the first one better
though as you can swap a & b around in each test to get (eg) sorted by
name ascending and then a date field descending.

Just use -x.b:

a.sort_by{|x| [x.name, -x.date]}

Sort_by is much faster than sort with a block.

gegroet,
Erik V.
 
M

Martin DeMello

That's a neat trick, thanks! I actually like the first one better
though as you can swap a & b around in each test to get (eg) sorted by
name ascending and then a date field descending.

Just use -x.b:

a.sort_by{|x| [x.name, -x.date]}

Doesn't always work. I keep this handy:

class RevCmp
attr_reader :this

def initialize(obj)
@this = obj
end

def <=>(other)
other.this <=> @this
end

# not delegating anything else because this is explicitly a throwaway
# object used only inside a sort_by block
end

and then you have

a.sort_by {|x| [x.name, RevCmp.new(x.date)]}

you could even use a top-level method so you can say

a.sort_by {|x| [x.name, descending(x.date)]}

where descending(x) returns RevCmp.new(x)

You could even mix it into object to get

a.sort_by {|x| [x.name, x.date._descending_]}

where I use the underscores as a cosmetic way of making it stand out
inside the sort block

martin
 
P

Pit Capitain

2008/8/11 James Coglan said:
objects.sort do |a,b|
comp = (a.lastname <=> b.lastname)
comp.zero? ? (a.firstname <=> b.firstname) : comp
end

For this usecase there's also Numeric#nonzero?

objects.sort do |a, b|
(a.lastname <=> b.lastname).nonzero? ||
(a.firstname <=> b.firstname)
end

Regards,
Pit
 
M

Max Williams

Pit said:
objects.sort do |a, b|
(a.lastname <=> b.lastname).nonzero? ||
(a.firstname <=> b.firstname)
end

Wow, this is all great stuff. thanks folks.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top