stable sort_by?

P

Patrick Gundlach

Hi,

I have to sort a list using a number of criterions (a,b,c,d) where d
is the least important criterion and a is the most important one. All
comparisons should be 'higher number: better position'. This is what I
have tried:

--------------------------------------------------
require 'pp'

h= {"Team A"=>{:a=>3, :b=>2, :c=>6, :d=>114 },
"Team B"=>{:a=>0, :b=>-2, :c=>4, :d=>112 },
"Team C"=>{:a=>3, :b=>4, :c=>4, :d=>110 },
"Team D"=>{:a=>3, :b=>4, :c=>4, :d=>108 },
}

tmp=h.to_a

[:d,:c,:b,:a].each do |criterion|
tmp=tmp.sort_by { |a| a[1][criterion] }.reverse
end

pp tmp

--------------------------------------------------

[["Team D", {:d=>108, :a=>3, :b=>4, :c=>4}],
["Team C", {:d=>110, :a=>3, :b=>4, :c=>4}],
["Team A", {:d=>114, :a=>3, :b=>2, :c=>6}],
["Team B", {:d=>112, :a=>0, :b=>-2, :c=>4}]]


but as far as I can see 'Team C' should be better than 'Team D',
because of criterion d. Is it possible that sort_by is not stable? Or
is there something I did wrong?

Patrick
 
P

Patrick Gundlach

A follow-up:
--------------------------------------------------
require 'pp'

h= {"Team A"=>{:a=>3, :b=>2, :c=>6, :d=>114 },
"Team B"=>{:a=>0, :b=>-2, :c=>4, :d=>112 },
"Team C"=>{:a=>3, :b=>4, :c=>4, :d=>110 },
"Team D"=>{:a=>3, :b=>4, :c=>4, :d=>108 },
}

this seems to be stable:

pp h.sort{ |a,b|
res = a[1][:a] <=> b[1][:a]
if res == 0
res = a[1][:b] <=> b[1][:b]
end
if res == 0
res = a[1][:c] <=> b[1][:c]
end
if res == 0
res = a[1][:d] <=> b[1][:d]
end
res
}.reverse

But I still would like to know if sort_by is unstable (or if am I
doing something wrong).

Patrick
 
F

Frederick Ros

Patrick said:
Hi,

I have to sort a list using a number of criterions (a,b,c,d) where d
is the least important criterion and a is the most important one. All
comparisons should be 'higher number: better position'. This is what I
have tried:

--------------------------------------------------
require 'pp'

h= {"Team A"=>{:a=>3, :b=>2, :c=>6, :d=>114 },
"Team B"=>{:a=>0, :b=>-2, :c=>4, :d=>112 },
"Team C"=>{:a=>3, :b=>4, :c=>4, :d=>110 },
"Team D"=>{:a=>3, :b=>4, :c=>4, :d=>108 },
}

tmp=h.to_a

[:d,:c,:b,:a].each do |criterion|
tmp=tmp.sort_by { |a| a[1][criterion] }.reverse
end

pp tmp

--------------------------------------------------

[["Team D", {:d=>108, :a=>3, :b=>4, :c=>4}],
["Team C", {:d=>110, :a=>3, :b=>4, :c=>4}],
["Team A", {:d=>114, :a=>3, :b=>2, :c=>6}],
["Team B", {:d=>112, :a=>0, :b=>-2, :c=>4}]]


but as far as I can see 'Team C' should be better than 'Team D',
because of criterion d. Is it possible that sort_by is not stable? Or
is there something I did wrong?


Wouldn't this be simpler ? :

h.sort_by {|k,v| [v[:a],v[:b],v[:c],v[:d]]}.reverse

Best Regards,
Fred
 
C

Christer Nilsson

Or using Array instead of Hash:

I don't think sort is stable. You have to specify the sorting criteria,
and sort only once.

require 'test/unit'
class TestSortBy < Test::Unit::TestCase
def test_all
a= [["A", 3, 2, 6, 114],
["B", 0,-2, 4, 112],
["C", 3, 4, 4, 110],
["D", 3, 4, 4, 108]]

assert_equal [["A", 3, 2, 6, 114],
["C", 3, 4, 4, 110],
["D", 3, 4, 4, 108],
["B", 0, -2, 4, 112]],
a.sort_by {|name,x,y,z,w| [x, w] }.reverse
end
end

Christer
 
D

dblack

Hi --

Wouldn't this be simpler ? :

h.sort_by {|k,v| [v[:a],v[:b],v[:c],v[:d]]}.reverse

Or:

h.sort_by {|k,v| v.values_at:)a,:b,:c,:d) }.reverse

(Possibly a little clearer visually.)


David
 
K

Kevin Olbrich

Does the principle of 'least surprise' suggest that the default sort_by
algorithm should be modified to be stable?

It seems to me that a 'stable' algorithm is the expected behavior.
 
M

Mike Fletcher

Frederick said:
h.sort_by {|k,v| [v[:a],v[:b],v[:c],v[:d]]}.reverse

OK, this or something like it should be in the rdoc for sort_by. I was
doing something similar and wound up writing a similar sort with
multiple if tests like the OP because I didn't remember that Array
implements a sane <=>.

Also, would there be a similar cool way to do this in one step where you
want a mix of ascending and descending sorts on different parts? Say I
had a hash (in YAML):

Bobby:
age: 11
lastname: Smith
Suzy:
age: 13
lastname: Jones
Ted:
age 12
lastname: Smith

And I wanted to sort
- alphabetically by lastname
- then age oldest to youngest

So in Perl I'd do something like:

my @sorted_keys = sort { $a->{lastname} cmp $b->{lastname}
|| $b->{age} <=> $a->{age} }
keys %data;

In this case I could use

sorted_keys = data.sort_by { |k,v| [ v["lastname"], -1*v["age"] ] }

But what if I wanted reverse alphabetically by lastname (Z-A)? In perl
I'd swap $a and $b in the first comparison, but I can't think of a
spiffy way to do the same using sort_by.
 
P

Patrick Gundlach

pp h.sort{ |a,b|
res = a[1][:a] <=> b[1][:a]
if res == 0
res = a[1][:b] <=> b[1][:b]
end
if res == 0
res = a[1][:c] <=> b[1][:c]
end
if res == 0
res = a[1][:d] <=> b[1][:d]
end
res
}.reverse

Checkout Numeric#nonzero?.


.... learning a new thing everyday... thanks.

Patrick
 
M

Martin DeMello

Mike Fletcher said:
In this case I could use

sorted_keys = data.sort_by { |k,v| [ v["lastname"], -1*v["age"] ] }

But what if I wanted reverse alphabetically by lastname (Z-A)? In perl
I'd swap $a and $b in the first comparison, but I can't think of a
spiffy way to do the same using sort_by.

class Reverser
attr_accessor :eek:bj

def initialize(obj)
@obj = obj
end

def <=>(other)
other.obj <=> self.obj
end
end

class Object
def rev
Reverser.new(self)
end
end

require 'yaml'

data = YAML.load %{
Bobby:
age: 11
lastname: Smith
Suzy:
age: 13
lastname: Jones
Ted:
age: 12
lastname: Smith
}

p data

sorted_keys_0 = data.sort_by {|k,v| [v["lastname"].rev, v["age"]]}
sorted_keys_1 = data.sort_by {|k,v| [v["lastname"], v["age"].rev]}

p sorted_keys_0
p sorted_keys_1

martin
 

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,997
Messages
2,570,241
Members
46,831
Latest member
RusselWill

Latest Threads

Top