Array.sort when it's items are String inheritors with redefined <=> works like if not redefined

M

MiG

Hello,
I want to have a string which, if in array, will be sorted like numbers=
=20
I wrote this:

-------------------------------------------------------------------------=
----------------

class String2 < String

def <=3D> str2
self.to_i <=3D> str2.to_i
end

end

a =3D [ String2.new('1'), String2.new('10'), String2.new('5') ]

puts a.sort.join(',')

-------------------------------------------------------------------------=
----------------

It produces "1,10,5", but I expected "1,5,10"

Then I wrote a class without String inheritance and it works.
BUT: another strange thing happened:

in `<=3D>': undefined method `to_i' for #<S:0x40020930 @a=3D"10"> =20
(NoMethodError)

to_i method, even if @a is a String, must be explicitely defined.
Moreover "defined". What do you think about it?

-------------------------------------------------------------------------=
----------------

class String2

def initialize a
@a =3D a
end

def <=3D> b
@a.is_a? String # >> true but..
@a.to_i <=3D> b.to_i # .. @a.to_i doesn't work if I don't defin=
e =20
to_i method below
end

def to_i
@a.to_i
end

def to_s
@a
end

end

a =3D [ String2.new('1'), String2.new('10'), String2.new('5') ]

puts a.sort.join(',')

-------------------------------------------------------------------------=
 
R

Robert Klemme

MiG said:
Hello,
I want to have a string which, if in array, will be sorted like
numbers
I wrote this:

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

class String2 < String

def <=> str2
self.to_i <=> str2.to_i
end

end

a = [ String2.new('1'), String2.new('10'), String2.new('5') ]

puts a.sort.join(',')

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

It produces "1,10,5", but I expected "1,5,10"

I think there is an optimization going on that doesn't use <=> for String
and subclasses. This is one of the reasons why it's subclassing of core
classes like String, Array etc. should be done rarely and with care.

In your case you better use sort_by:
a=["1","10","5"] => ["1", "10", "5"]
a.sort_by {|x| x.to_i}
=> ["1", "5", "10"]

This works also if the array contains instances of your subclass. It
might also be more efficient as #to_i is only invoked once per instance
and not once per comparison per compared object.
Then I wrote a class without String inheritance and it works.
BUT: another strange thing happened:

in `<=>': undefined method `to_i' for #<S:0x40020930 @a="10">
(NoMethodError)

to_i method, even if @a is a String, must be explicitely defined.
Moreover "defined". What do you think about it?

That's not strange. That's perfectly normal. Because there is no default
#to_i method:
NoMethodError: undefined method `to_i' for #<Object:0x101d0b28>
from (irb):3
from :0

Kind regards

robert
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Array.sort when it's items are String inheritors with redefined <=> works like if not redefined"

|Hello,
| I want to have a string which, if in array, will be sorted like numbers.
|I wrote this:
|
|-----------------------------------------------------------------------------------------
|
|class String2 < String
|
| def <=> str2
| self.to_i <=> str2.to_i
| end
|
|end
|
|a = [ String2.new('1'), String2.new('10'), String2.new('5') ]
|
|puts a.sort.join(',')

Why not use sort_by, much simpler solution?

a = ['1', '10', '5']
puts a.sort_by{|x|x.to_u}.join(',')

matz.
 
J

James Edward Gray II

Why not use sort_by, much simpler solution?

a = ['1', '10', '5']
puts a.sort_by{|x|x.to_u}.join(',')

I believe that second line is supposed to read:

puts a.sort_by{|x|x.to_i}.join(',')

James Edward Gray II
 
A

Ara.T.Howard

Hello,
I want to have a string which, if in array, will be sorted like numbers. I
wrote this:

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

class String2 < String

def <=> str2
self.to_i <=> str2.to_i
end

end

a = [ String2.new('1'), String2.new('10'), String2.new('5') ]

puts a.sort.join(',')

put the method in Array:

harp:~ > cat a.rb
class Array
def sort_as!
map!{|elem| yield elem}
sort!
self
end
def sort_as
dup.sort!
end
end


a = %w( 1 10 5 )

p a.sort_as{|s| Integer s}
p a.sort_as{|s| Float s}
p a.sort_as{|s| s.reverse }

a.sort_as!{|s| Integer s}
p a


harp:~ > ruby a.rb
["1", "10", "5"]
["1", "10", "5"]
["1", "10", "5"]
[1, 5, 10]

this avoids calling to_i, to_f, or whatever multiple times on the same object,
which will occur if you use either a spacship (<=>) operator or sort_by
approach.


hth.


-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| anything that contradicts experience and logic should be abandoned.
| -- h.h. the 14th dalai lama
===============================================================================
 
S

Sean O'Halpin

this avoids calling to_i, to_f, or whatever multiple times on the same ob= ject,
which will occur if you use either a spacship (<=3D>) operator or sort_by
approach.

I thought sort_by was a packaged form of the Schwartzian transform, i.e.

class A
def to_i
puts "in A.to_i"
3
end
end

p [1, 2, 3, 4, 5, A.new].sort_by {|x| x.to_i }
# is equivalent to
p [1, 2, 3, 4, 5, A.new].map{|x| [x.to_i, x]}.sort{|y, z| y[0] <=3D>
z[0]}.map{|x| x[1]}

__END__
in A.to_i
[1, 2, 3, #<A:0x2870f28>, 4, 5]
in A.to_i
[1, 2, 3, #<A:0x2870cd0>, 4, 5]

Regards,

Sean
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Array.sort when it's items are String inheritors with redefined <=> works like if not redefined"

|> a = ['1', '10', '5']
|> puts a.sort_by{|x|x.to_u}.join(',')
|
|I believe that second line is supposed to read:
|
| puts a.sort_by{|x|x.to_i}.join(',')

Oops, you're right. Thank you for correction.

matz.
 
A

Ara.T.Howard

this avoids calling to_i, to_f, or whatever multiple times on the same object,
which will occur if you use either a spacship (<=>) operator or sort_by
approach.

I thought sort_by was a packaged form of the Schwartzian transform, i.e.

class A
def to_i
puts "in A.to_i"
3
end
end

p [1, 2, 3, 4, 5, A.new].sort_by {|x| x.to_i }
# is equivalent to
p [1, 2, 3, 4, 5, A.new].map{|x| [x.to_i, x]}.sort{|y, z| y[0] <=>
z[0]}.map{|x| x[1]}

__END__
in A.to_i
[1, 2, 3, #<A:0x2870f28>, 4, 5]
in A.to_i
[1, 2, 3, #<A:0x2870cd0>, 4, 5]

you are quite right sean - i guess that only applies to the op's original

a.to_i <=> b.to_i

where you could end up doing that more than once.

regards.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| anything that contradicts experience and logic should be abandoned.
| -- h.h. the 14th dalai lama
===============================================================================
 
R

Ryan Leavengood

I think you have a bug here, Ara:

harp:~ > cat a.rb
class Array
def sort_as!
map!{|elem| yield elem}
sort!
self
end
def sort_as
dup.sort! #???
end

How about:

def sort_as(&block)
dup.sort_as!(&block)
end
end


a =3D %w( 1 10 5 )

p a.sort_as{|s| Integer s}
p a.sort_as{|s| Float s}
p a.sort_as{|s| s.reverse }

a.sort_as!{|s| Integer s}
p a

C:\_Ryan\ruby>ruby a.rb
[1, 5, 10]
[1.0, 5.0, 10.0]
["01", "1", "5"]
[1, 5, 10]

I'm not sure it is worth adding such a method, when a map{}.sort would
do the same thing, and is more explicit. Plus we have sort_by to solve
the OP's problem.

Ryan
 

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

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,040
Latest member
papereejit

Latest Threads

Top