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

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

···

--
Posted via http://www.ruby-forum.com/.

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

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

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

···

--
Posted via http://www.ruby-forum.com/.

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

···

--
Posted via http://www.ruby-forum.com/\.

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

···

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

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

···

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

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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.

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.

···

--
Posted via http://www.ruby-forum.com/\.

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
module Enumerable - RDoc Documentation for more
information, and run some benchmarks for your use case if it's a big issue.

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