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
on 2008-08-11 17:18
on 2008-08-11 17:30
> > 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
on 2008-08-11 17:30
On Mon, Aug 11, 2008 at 5:16 PM, Max Williams <toastkid.williams@gmail.com> wrote: > 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.
on 2008-08-11 17:34
Jesús Gabriel y Galán wrote: > 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
on 2008-08-11 17:51
> 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]} Sort_by is much faster than sort with a block. gegroet, Erik V.
on 2008-08-11 18:00
> Sort_by is much faster than sort with a block. This is often true as sort_by only calls methods on each object once, but it is not universally true -- see http://ruby-doc.org/core/classes/Enumerable.html#M003151 for more information, and run some benchmarks for your use case if it's a big issue.
on 2008-08-11 19:40
On Mon, Aug 11, 2008 at 8:48 AM, Erik Veenstra <erikveen@gmail.com> wrote: >> 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
on 2008-08-11 20:38
2008/8/11 James Coglan <jcoglan@googlemail.com>: > > 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
on 2008-08-12 10:22
Pit Capitain wrote: > objects.sort do |a, b| > (a.lastname <=> b.lastname).nonzero? || > (a.firstname <=> b.firstname) > end Wow, this is all great stuff. thanks folks.
on 2013-02-18 18:42
I would just create an array containing the parts you want to compare:
objects.sort { |a,b| [a.lastname, a.firstname] <=> [b.lastname,
b.firstname] }
on 2013-02-18 23:02
On Mon, Feb 18, 2013 at 6:42 PM, Jack V. <lists@ruby-forum.com> wrote: > I would just create an array containing the parts you want to compare: > > objects.sort { |a,b| [a.lastname, a.firstname] <=> [b.lastname, > b.firstname] } In theory this is less efficient since there are two Array instances created per comparison. If you go for the Array solution, using #sort_by is probably better: objects.sort_by {|a| [a.lastname, a.firstname]} Kind regards robert
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.