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(',')

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

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 `<=>': 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?

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

class String2

     def initialize a
         @a = a
     end

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

     def to_i
         @a.to_i
     end

     def to_s
         @a
     end

end

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

puts a.sort.join(',')

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

Thank you,
jan molic

MiG wrote:

···

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:

Object.new.to_i

NoMethodError: undefined method `to_i' for #<Object:0x101d0b28>
        from (irb):3
        from :0

Kind regards

    robert

Hi,

···

In message "Re: Array.sort when it's items are String inheritors with redefined <=> works like if not redefined" on Wed, 19 Oct 2005 22:28:29 +0900, MiG <mig@1984.cz> writes:

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.

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

···

On Wed, 19 Oct 2005, MiG wrote:

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(',')

--

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

===============================================================================

I believe that second line is supposed to read:

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

James Edward Gray II

···

On Oct 19, 2005, at 8:59 AM, Yukihiro Matsumoto wrote:

Why not use sort_by, much simpler solution?

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

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]

Regards,

Sean

···

On 10/19/05, Ara.T.Howard <Ara.T.Howard@noaa.gov> wrote:

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 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 = %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

···

On 10/19/05, Ara.T.Howard <Ara.T.Howard@noaa.gov> wrote:

Hi,

···

In message "Re: Array.sort when it's items are String inheritors with redefined <=> works like if not redefined" on Wed, 19 Oct 2005 23:06:04 +0900, James Edward Gray II <james@grayproductions.net> writes:

  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.

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

···

On Wed, 19 Oct 2005, Sean O'Halpin wrote:

On 10/19/05, Ara.T.Howard <Ara.T.Howard@noaa.gov> wrote:

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]

--

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

===============================================================================