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

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

On Mon, Aug 11, 2008 at 5:16 PM, Max W.
[email protected] 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.

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

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.

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.

On Mon, Aug 11, 2008 at 8:48 AM, Erik V. [email protected]
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

2008/8/11 James C. [email protected]:

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

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

Pit C. 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 Mon, Feb 18, 2013 at 6:42 PM, Jack V. [email protected] 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

Can someone explain to me why it works?

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

Why does this kind of sorting work on an Array input?

How would I know that it first starts by the first
Array member, and then the second? Why is it not
the other way (from last to first, rather from first
to last entry)?

Robert H. wrote in post #1155030:

Can someone explain to me why it works?

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

Why does this kind of sorting work on an Array input?

When an Array calls a method, the block specified after
the method call does not have to take an array as
an argument. Here is an example:

class Array
def do_stuff

if block_given?
  map do |array_elmt|
    yield array_elmt
  end
else
  self
end

end
end

data = [10, 20, 30]

y = data.do_stuff { |x| x*2 }
p y

y = data.do_stuff
p y

–output:–
[20, 40, 60]
[10, 20, 30]

Enumerable#sort_by() uses a block in a similar fashion.
sort_by() sends each
element of an Array that you want to sort to the block to create a new
value that it will use
as a stand in for the original value when sorting. The return values
from the block are what sort_by() uses as the values to sort. For
example, look at this Hash:

mapped_orig_vals = {

['David', 2] => objA
['David', 1] => objB,

}

After creating each entry in the hash, sort_by() sorts the keys, which
are arrays, to produce:

ordered_keys = [

[‘David’, 1],
[‘David’, 2],

]

After sorting the keys, sort_by() looks up the value
associated with each key to get the original value, e.g.

ordered_orig_vals = ordered_keys.map do |key|
mapped_orig_values[key]
end
#=>[objB, objA]

How would I know that it first starts by the first
Array member, and then the second?

If by Array you mean the Array returned by the sort_by() block, you
would know by reading about how ruby determines whether two
arrays are equal or not:

In every computer programming language I’ve studied, things like Arrays
and Strings are always compared by starting with the first
element/character, and if they are the same, then the second element is
compared, etc. Once a difference is found, then one Array/String is
considered smaller than the other Array/String. If all the elements are
the same but one Array/String is shorter than the other, the shorter one
is considered smaller. If all the elements are the same, and they are
the same length, then they are considered equal.

On the other hand, if by Array you mean the original Array you are
trying to sort, it is irrelevant which end of the array you start at.

Just use Enumerable#sort: http://www.ruby-doc.org
and you know this video http://youtu.be/1DzlYY4saMY