Multi-level sort idiom?

Let’s say I have an array, each element of which is a 2-dimensional array
of ints.

I want to sort with the first element in the 2D array taking precedence,
but using the second as a tiebreaker.

So far, I haven’t come up with a nice idiom that doesn’t seem a bit kludgy:
What I tried was to do something like:
x.sort {|a,b| a[0]<=>b[0] || a[1]<=>b[1]}
but 0 is not false in Ruby so this doesn’t work.

x.sort {|a,b| 2*(a[0]<=>b[0]) + (a[1]<=>b[1])}
works, but is non-intuitive and not pretty.

Does anyone have a nice clean rubyesque idiom for this?

You can use Numeric’s nonzero? e.g.

x = [[5, 1], [3, 2], [3, 3], [1, 5]]
x.sort { |a, b| (a[0] <=> b[0]).nonzero? || a[1] <=> b[1] }

=> [[1, 5], [3, 2], [3, 3], [5, 1]]

Hope this helps,

Mike

···

In article 1031682194.811634@cswreg.cos.agilent.com, Brett Williams wrote:

Let’s say I have an array, each element of which is a 2-dimensional array
of ints.

I want to sort with the first element in the 2D array taking precedence,
but using the second as a tiebreaker.

So far, I haven’t come up with a nice idiom that doesn’t seem a bit kludgy:
What I tried was to do something like:
x.sort {|a,b| a[0]<=>b[0] || a[1]<=>b[1]}
but 0 is not false in Ruby so this doesn’t work.

x.sort {|a,b| 2*(a[0]<=>b[0]) + (a[1]<=>b[1])}
works, but is non-intuitive and not pretty.

Does anyone have a nice clean rubyesque idiom for this?


mike@stok.co.uk | The “`Stok’ disclaimers” apply.
http://www.stok.co.uk/~mike/ | GPG PGP Key 1024D/059913DA
mike@exegenix.com | Fingerprint 0570 71CD 6790 7C28 3D60
http://www.exegenix.com/ | 75D2 9EC4 C1C0 0599 13DA

Rubyesque idiom? Probably not, but this is readable:

y = x.sort{|a,b|
if (result = a[0]<=>b[0]) != 0
result
else
a[1]<=>b[1]
end
}

···

On Tuesday 10 September 2002 01:35 pm, Brett Williams wrote:

Let’s say I have an array, each element of which is a 2-dimensional array
of ints.

I want to sort with the first element in the 2D array taking precedence,
but using the second as a tiebreaker.

So far, I haven’t come up with a nice idiom that doesn’t seem a bit kludgy:
What I tried was to do something like:
x.sort {|a,b| a[0]<=>b[0] || a[1]<=>b[1]}
but 0 is not false in Ruby so this doesn’t work.

x.sort {|a,b| 2*(a[0]<=>b[0]) + (a[1]<=>b[1])}
works, but is non-intuitive and not pretty.

Does anyone have a nice clean rubyesque idiom for this?

Mike Stok wrote:

In article 1031682194.811634@cswreg.cos.agilent.com, Brett Williams
wrote:

Let’s say I have an array, each element of which is a 2-dimensional array
of ints.

I want to sort with the first element in the 2D array taking precedence,
but using the second as a tiebreaker.
[snip]
You can use Numeric’s nonzero? e.g.

x = [[5, 1], [3, 2], [3, 3], [1, 5]]
x.sort { |a, b| (a[0] <=> b[0]).nonzero? || a[1] <=> b[1] }

=> [[1, 5], [3, 2], [3, 3], [5, 1]]

Thanks, that’s what I was looking for. Now I understand the behavior of
Ruby’s ||, &&, or, and and better too!

···


The key of strategy… is not to choose a path that leads to victory, but
to choose so that all paths lead to a victory.
– L.M. Bujold

In this case

x.sort { |a,b| a <=> b }

or even

x.sort

would do the right thing already. Ruby’s objects often know how to
compare to each other already.

···

On Tue, 2002-09-10 at 20:35, Mike Stok wrote:

You can use Numeric’s nonzero? e.g.

x = [[5, 1], [3, 2], [3, 3], [1, 5]]
x.sort { |a, b| (a[0] <=> b[0]).nonzero? || a[1] <=> b[1] }

=> [[1, 5], [3, 2], [3, 3], [5, 1]]


C++ : an octopus made by nailing extra legs onto a dog.

Mike Stok wrote:

You can use Numeric’s nonzero? e.g.

This won’t work, as it doesn’t return 1, 0 or -1 for a[0]<=>b[0]

···


Clifford Heath

I was wrong. Interesting behaviour - nice but confusing - I would have
thought that what with operators being mostly implemented as methods,
the result of nonzero? would be returned instead of the result of
a[0]<=>b[0]. Is there a definitive statement of which operators/methods
modify the “last expression executed”?

···


Clifford Heath

The result of nonzero? does get returned, effectively nonzero?
transforms any number except 0 to itself, and 0 is transformed to a
false value, nil.

0.nonzero? => nil
1.5.nonzero? => 1.5
(-1.5).nonzero? => -1.5

so

(a <=> b).nonzero?

will be the result of a <=> b, except in the case when it’s 0.

Hope this helps,

Mike

···

In article 3D812169.58D388D8@managesoft.com, Clifford Heath wrote:

I was wrong. Interesting behaviour - nice but confusing - I would have
thought that what with operators being mostly implemented as methods,
the result of nonzero? would be returned instead of the result of
a[0]<=>b[0]. Is there a definitive statement of which operators/methods
modify the “last expression executed”?


mike@stok.co.uk | The “`Stok’ disclaimers” apply.
http://www.stok.co.uk/~mike/ | GPG PGP Key 1024D/059913DA
mike@exegenix.com | Fingerprint 0570 71CD 6790 7C28 3D60
http://www.exegenix.com/ | 75D2 9EC4 C1C0 0599 13DA

Historically, nonzero? was introduced for very this kind of sort (what
is the correct name?). But Array#<=> was introduced in no time after
nonzero? was. Tadayoshi Funaba, who is the inventor of nonzero? and
Array#<=>, said that that Array#<=> might obsolets nonzero?. He
thought that a user however could choose favorite one [ruby-list:8546].
I prefer Array#<=> way.

In 1.7.x, we can do
x.sort_by{|a| [a[1],a[2],a[0]]}

instead of
x.sort{|a,b| [a[1],a[2],a[0]] <=> [b[1],b[2],b[0]]}

and the former is faster than the latter because sort_by implements
Schwartzian transform(*1) in C.

sort_by in Ruby is defined in shim(*2) for 1.6.x as follows:
module Enumerable
def sort_by
ary = map { |i| [yield(i), i] }
ary.sort! { |a, b| a[0] <=> b[0] }
ary.map! { |i| i[1] }
end
end

(*1) http://www.5sigma.com/perl/schwtr.html
(*2) http://www.ruby-lang.org/~knu/cgi-bin/cvsweb.cgi/shim/ruby16/lib/features/ruby18/enumerable.rb

– Gotoken

···

At Fri, 13 Sep 2002 09:21:37 +0900, Mike Stok wrote:

The result of nonzero? does get returned, effectively nonzero?
transforms any number except 0 to itself, and 0 is transformed to a
false value, nil.