Array.which_long? ( I coded an extension for Array )

Hi --

> Thanks for your reply, I learned more on this thread :stuck_out_tongue:
> But I have a question:
> If I have an array contain:
> ary = [1, 12, 234, "456"]
> there has two elements which size is 3, but the longest method just returned
> one of them.
> I can't solve it :frowning:

Alternatively I can use my erroneous code from above to give you all
longest elements
def longest
   longest_size = map{ |e| [e.to_s.size, e.to_s]}.max.first
   reject{ |x| x.size < longest}
end

this is slow though, maybe the following is preferable - but not so readable -
  def longest
           inject(){ |s, e|
                  if s.empty? || s.first.size < e.to_s.size then [e]
                  elsif s.first.size == e.to_s.size then s << e
                  else s
                  end
                }
end

<tie breaking solutions snipped>

Robert
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

···

On 4/29/07, David A. Black <dblack@wobblini.net> wrote:

On 4/29/07, Billy Hsu <ruby.maillist@gmail.com> wrote:

Hi,
Harry, thanks :smiley:

···

On 4/30/07, Harry <list.push@gmail.com> wrote:

On 4/29/07, Billy Hsu <ruby.maillist@gmail.com> wrote:
> Thanks for your reply, I learned more on this thread :stuck_out_tongue:
> But I have a question:
> If I have an array contain:
> ary = [1, 12, 234, "456"]
> there has two elements which size is 3, but the longest method just
returned
> one of them.
> I can't solve it :frowning:
>

Is this what you are looking for?
Do you want all longest elements?

big = [1, 12, 234,45,978, "456"].max {|x,y| x.to_s.size <=> y.to_s.size}
p [1, 12, 234,45,978, "456"].select {|r| r.to_s.size == big.to_s.size}

Harry

--
http://www.kakueki.com/ruby/list.html
A Look into Japanese Ruby List in English

--
Taiwan Ruby Users Group:
http://www.ruby.oss.tw/
http://www.ruby.oss.tw/phpbb

I am sorry, I did not see it.

I was just kidding Robert.

But I *did* overlook your posting. Honestly. :slight_smile:

> But it was to return an array of all longest elements, I guess you can
> maybe refine it, it seems clumsy, so I repost it just if you have some
> time to play :wink:
>
> def longest
> inject(){ |s, e|
> if s.empty? || s.first.size < e.to_s.size then [e]
> elsif s.first.size == e.to_s.size then s << e
> else s
> end
> }
> end

Hm, I'd probably use case here. Let's see...

def longest
   inject() do |lg, e|
     case
       when lg.empty?, lg.first.size == e.size
         lg << e
       when lg.first.size < e.size
         [e]
       else
         lg
     end
   end
end

better already, I am still looking for *the* solution

Alternative, but with higher memory consumption

def longest
   lg = Hash.new {|h,k| h[k] = }
   each {|x| lg[x.size] << x}
   lg.sort_by {|k,v| k}.last.pop
end

good idea but I wanted inject :slight_smile:

<snip>

That's an easy transformation (left as exercise for the reader, bonus points for a one liner). :slight_smile:

Cheers

  robert

···

On 30.04.2007 16:11, Robert Dober wrote:

On 4/30/07, Robert Klemme <shortcutter@googlemail.com> wrote:

<snip>

>>
>> def longest
>> lg = Hash.new {|h,k| h[k] = }
>> each {|x| lg[x.size] << x}
>> lg.sort_by {|k,v| k}.last.pop
>> end
> good idea but I wanted inject :slight_smile:
>> <snip>

That's an easy transformation (left as exercise for the reader, bonus
points for a one liner). :slight_smile:

inject( Hash.new{ |h,k| h[k]=} ){|h,e| h[e.size] << e}.sort_by....

as this is not readable anymore let me golf a little bit

inject(){|a,e| a[e.size] = (a[e.size]||)+[e];a}.last
hmm that is not too bad :wink:

Robert

···

On 4/30/07, Robert Klemme <shortcutter@googlemail.com> wrote:

Cheers

        robert

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

<snip>

>>
>> def longest
>> lg = Hash.new {|h,k| h[k] = }
>> each {|x| lg[x.size] << x}
>> lg.sort_by {|k,v| k}.last.pop
>> end
> good idea but I wanted inject :slight_smile:
>> <snip>

That's an easy transformation (left as exercise for the reader, bonus
points for a one liner). :slight_smile:

inject( Hash.new{ |h,k| h[k]=} ){|h,e| h[e.size] << e}.sort_by....

                                                       ^^^^
The return from the block is missing. :slight_smile:

as this is not readable anymore let me golf a little bit

inject(){|a,e| a[e.size] = (a[e.size]||)+[e];a}.last
hmm that is not too bad :wink:

Couldn't you just use ||= here?

inject(){|a,e|(a[e.size]||=)<<e;a}.last

I like your idea to use the length as array index - that way no sorting is needed. Brilliant!

Kind regards

  robert

···

On 01.05.2007 00:03, Robert Dober wrote:

On 4/30/07, Robert Klemme <shortcutter@googlemail.com> wrote:

> <snip>
>> >>
>> >> def longest
>> >> lg = Hash.new {|h,k| h[k] = }
>> >> each {|x| lg[x.size] << x}
>> >> lg.sort_by {|k,v| k}.last.pop
>> >> end
>> > good idea but I wanted inject :slight_smile:
>> >> <snip>
>>
>> That's an easy transformation (left as exercise for the reader, bonus
>> points for a one liner). :slight_smile:
>
> inject( Hash.new{ |h,k| h[k]=} ){|h,e| h[e.size] << e}.sort_by....
                                                       ^^^^
The return from the block is missing. :slight_smile:

Yeah I did not like this so I did not test it, I forgot the return of
the block in the solution below too, but I liked the solution and
therefore tested it....

> as this is not readable anymore let me golf a little bit
>
> inject(){|a,e| a[e.size] = (a[e.size]||)+[e];a}.last
> hmm that is not too bad :wink:

Couldn't you just use ||= here?

inject(){|a,e|(a[e.size]||=)<<e;a}.last

Indeed, I guess I was very tired!!!
Now the solutions complexity and length just seems right.
Thanks for getting this right.

I like your idea to use the length as array index - that way no sorting
is needed. Brilliant!

Well that is grossly exaggerated, but thank you anyway, the idea was
yours of course I just used an Array instead of a Hash, that must be
my tiny Lua background.

Cheers
Robert

···

On 5/1/07, Robert Klemme <shortcutter@googlemail.com> wrote:

On 01.05.2007 00:03, Robert Dober wrote:
> On 4/30/07, Robert Klemme <shortcutter@googlemail.com> wrote:

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

I wondered whether these rather convoluted solutions had the
redeeming feature of being faster than a simple and natural
solution. It turned out that they are slower:

             user system total real
simple 0.671000 0.010000 0.681000 ( 0.711000)
dober1 1.472000 0.010000 1.482000 ( 1.512000)
klemme1 1.261000 0.000000 1.261000 ( 1.292000)
klemme2 2.324000 0.010000 2.334000 ( 2.363000)
dober2 1.903000 0.000000 1.903000 ( 1.963000)
klemme3 1.211000 0.000000 1.211000 ( 1.242000)
martin 1.663000 0.000000 1.663000 ( 1.692000)

Here's the benchmark code:

require 'benchmark'

# Find longest strings.
class Array
  def simple
    max = map{|s| s.size}.max
    select{|s| s.size == max}
  end

  def dober1
    inject(){ |s, e|
      if s.empty? || s.first.size < e.to_s.size then [e]
      elsif s.first.size == e.to_s.size then s << e
      else s
      end
    }
  end

  def klemme1
     inject() do |lg, e|
       case
         when lg.empty?, lg.first.size == e.size
           lg << e
         when lg.first.size < e.size
           [e]
         else
           lg
       end
     end
  end

  def klemme2
     lg = Hash.new {|h,k| h[k] = }
     each {|x| lg[x.size] << x}
     lg.sort_by {|k,v| k}.last.pop
  end

  def dober2
    inject(){|a,e| a[e.size] = (a[e.size]||)+[e]
      a}.last
  end

  def klemme3
    inject(){|a,e|(a[e.size]||=)<<e;a}.last
  end

  def martin
    inject([0,]) { |(s,l),e|
      if e.to_s.size == s then [s,l << e]
      elsif e.to_s.size < s then [s,l]
      else [e.to_s.size, [e]]
      end
    }[1]
  end

end

methods = %w(simple dober1
  klemme1 klemme2 dober2 klemme3 martin)

words = %w(posit that it suffices that the past
  is exempt from mutation)

# Verify that all methods produce same result.
p methods.map{|method| words.send( method )}.uniq
puts

Benchmark.bm( 7 ) do |bm|

  methods.each{|method|
    bm.report( method ) do
      10_000.times{ words.send( method ) }
    end
  }

end

···

On May 1, 2:26 am, "Robert Dober" <robert.do...@gmail.com> wrote:

On 5/1/07, Robert Klemme <shortcut...@googlemail.com> wrote:

> On 01.05.2007 00:03, Robert Dober wrote:
> > On 4/30/07, Robert Klemme <shortcut...@googlemail.com> wrote:
> > <snip>

> >> >> def longest
> >> >> lg = Hash.new {|h,k| h[k] = }
> >> >> each {|x| lg[x.size] << x}
> >> >> lg.sort_by {|k,v| k}.last.pop
> >> >> end
> >> > good idea but I wanted inject :slight_smile:
> >> >> <snip>

> >> That's an easy transformation (left as exercise for the reader, bonus
> >> points for a one liner). :slight_smile:

> > inject( Hash.new{ |h,k| h[k]=} ){|h,e| h[e.size] << e}.sort_by....
> ^^^^
> The return from the block is missing. :slight_smile:

Yeah I did not like this so I did not test it, I forgot the return of
the block in the solution below too, but I liked the solution and
therefore tested it....

> > as this is not readable anymore let me golf a little bit

> > inject(){|a,e| a[e.size] = (a[e.size]||)+[e];a}.last
> > hmm that is not too bad :wink:

> Couldn't you just use ||= here?

> inject(){|a,e|(a[e.size]||=)<<e;a}.last

Indeed, I guess I was very tired!!!
Now the solutions complexity and length just seems right.
Thanks for getting this right.

> I like your idea to use the length as array index - that way no sorting
> is needed. Brilliant!

Well that is grossly exaggerated, but thank you anyway, the idea was
yours of course I just used an Array instead of a Hash, that must be
my tiny Lua background.

Cheers
Robert

Actually you forgot an an even simpler solution - although it is not as short as the other ones. Probably better call it "more straightforward":

   def simple2
     res =
     len = 0

     each do |s|
       case s.length <=> len
         when 1
           len = s.length
           res.clear << s
         when 0
           res << s
         when -1
           # too short, ignore
         else
           raise "Wrong comparison result: #{s.length <=> len}"
       end
     end

     res
   end

13:00:10 [Temp]: ./maxing.rb
[["suffices", "mutation"]]

              user system total real
simple 0.235000 0.000000 0.235000 ( 0.239000)
simple2 0.219000 0.000000 0.219000 ( 0.187000)
dober1 0.437000 0.000000 0.437000 ( 0.435000)
klemme1 0.407000 0.000000 0.407000 ( 0.401000)
klemme2 0.734000 0.000000 0.734000 ( 0.765000)
dober2 0.656000 0.000000 0.656000 ( 0.658000)
klemme3 0.344000 0.000000 0.344000 ( 0.376000)
martin 0.531000 0.000000 0.531000 ( 0.529000)

The reason is - of course - that you need to traverse the array only once.

Btw, any idea why I'm seeing this if I replace "bm" with "bmbm"?

13:00:44 [Temp]: ./maxing.rb
[["suffices", "mutation"]]

Rehearsal -------------------------------------------
simple ./maxing.rb:99:in `send': undefined method `simple ' for #<Array:0x104226b4> (NoMethodError)
         from ./maxing.rb:99
         from ./maxing.rb:99:in `times'
         from ./maxing.rb:99
         from /usr/lib/ruby/1.8/benchmark.rb:293:in `measure'
         from /usr/lib/ruby/1.8/benchmark.rb:261:in `bmbm'
         from /usr/lib/ruby/1.8/benchmark.rb:259:in `each'
         from /usr/lib/ruby/1.8/benchmark.rb:259:in `bmbm'
         from ./maxing.rb:95

Even though "simple" is reported an instance method of Arrray?

Kind regards

  robert

···

On 02.05.2007 02:50, William James wrote:

On May 1, 2:26 am, "Robert Dober" <robert.do...@gmail.com> wrote:

On 5/1/07, Robert Klemme <shortcut...@googlemail.com> wrote:

On 01.05.2007 00:03, Robert Dober wrote:

On 4/30/07, Robert Klemme <shortcut...@googlemail.com> wrote:
<snip>

def longest
   lg = Hash.new {|h,k| h[k] = }
   each {|x| lg[x.size] << x}
   lg.sort_by {|k,v| k}.last.pop
end

good idea but I wanted inject :slight_smile:

<snip>

That's an easy transformation (left as exercise for the reader, bonus
points for a one liner). :slight_smile:

inject( Hash.new{ |h,k| h[k]=} ){|h,e| h[e.size] << e}.sort_by....

                                                       ^^^^
The return from the block is missing. :slight_smile:

Yeah I did not like this so I did not test it, I forgot the return of
the block in the solution below too, but I liked the solution and
therefore tested it....

as this is not readable anymore let me golf a little bit
inject(){|a,e| a[e.size] = (a[e.size]||)+[e];a}.last
hmm that is not too bad :wink:

Couldn't you just use ||= here?
inject(){|a,e|(a[e.size]||=)<<e;a}.last

Indeed, I guess I was very tired!!!
Now the solutions complexity and length just seems right.
Thanks for getting this right.

I like your idea to use the length as array index - that way no sorting
is needed. Brilliant!

Well that is grossly exaggerated, but thank you anyway, the idea was
yours of course I just used an Array instead of a Hash, that must be
my tiny Lua background.

Cheers
Robert

I wondered whether these rather convoluted solutions had the
redeeming feature of being faster than a simple and natural
solution. It turned out that they are slower:

             user system total real
simple 0.671000 0.010000 0.681000 ( 0.711000)
dober1 1.472000 0.010000 1.482000 ( 1.512000)
klemme1 1.261000 0.000000 1.261000 ( 1.292000)
klemme2 2.324000 0.010000 2.334000 ( 2.363000)
dober2 1.903000 0.000000 1.903000 ( 1.963000)
klemme3 1.211000 0.000000 1.211000 ( 1.242000)
martin 1.663000 0.000000 1.663000 ( 1.692000)

Here's the benchmark code:

require 'benchmark'

# Find longest strings.
class Array
  def simple
    max = map{|s| s.size}.max
    select{|s| s.size == max}
  end

  def dober1
    inject(){ |s, e|
      if s.empty? || s.first.size < e.to_s.size then [e]
      elsif s.first.size == e.to_s.size then s << e
      else s
      end
    }
  end

  def klemme1
     inject() do |lg, e|
       case
         when lg.empty?, lg.first.size == e.size
           lg << e
         when lg.first.size < e.size
           [e]
         else
           lg
       end
     end
  end

  def klemme2
     lg = Hash.new {|h,k| h[k] = }
     each {|x| lg[x.size] << x}
     lg.sort_by {|k,v| k}.last.pop
  end

  def dober2
    inject(){|a,e| a[e.size] = (a[e.size]||)+[e]
      a}.last
  end

  def klemme3
    inject(){|a,e|(a[e.size]||=)<<e;a}.last
  end

  def martin
    inject([0,]) { |(s,l),e|
      if e.to_s.size == s then [s,l << e]
      elsif e.to_s.size < s then [s,l]
      else [e.to_s.size, [e]]
      end
    }[1]
  end

end

methods = %w(simple dober1
  klemme1 klemme2 dober2 klemme3 martin)

words = %w(posit that it suffices that the past
  is exempt from mutation)

# Verify that all methods produce same result.
p methods.map{|method| words.send( method )}.uniq
puts

Benchmark.bm( 7 ) do |bm|

  methods.each{|method|
    bm.report( method ) do
      10_000.times{ words.send( method ) }
    end
  }

end

<interesting but long BM snipped>
Please be aware that inject is quite slow, I guess it could be
optimized but I am not sure.
I still think you should write the code you feel is the nicest/most
readable/most concise before optimizing.

That does *not* mean that I did not read the benchmarks with interest :slight_smile:

Cheers
Robert

···

On 5/2/07, William James <w_a_x_man@yahoo.com> wrote:

On May 1, 2:26 am, "Robert Dober" <robert.do...@gmail.com> wrote:
> On 5/1/07, Robert Klemme <shortcut...@googlemail.com> wrote:
>

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

>>>> <snip>
>>>>>>> def longest
>>>>>>> lg = Hash.new {|h,k| h[k] = }
>>>>>>> each {|x| lg[x.size] << x}
>>>>>>> lg.sort_by {|k,v| k}.last.pop
>>>>>>> end
>>>>>> good idea but I wanted inject :slight_smile:
>>>>>>> <snip>
>>>>> That's an easy transformation (left as exercise for the reader, bonus
>>>>> points for a one liner). :slight_smile:
>>>> inject( Hash.new{ |h,k| h[k]=} ){|h,e| h[e.size] << e}.sort_by....
>>> ^^^^
>>> The return from the block is missing. :slight_smile:
>> Yeah I did not like this so I did not test it, I forgot the return of
>> the block in the solution below too, but I liked the solution and
>> therefore tested it....

>>>> as this is not readable anymore let me golf a little bit
>>>> inject(){|a,e| a[e.size] = (a[e.size]||)+[e];a}.last
>>>> hmm that is not too bad :wink:
>>> Couldn't you just use ||= here?
>>> inject(){|a,e|(a[e.size]||=)<<e;a}.last
>> Indeed, I guess I was very tired!!!
>> Now the solutions complexity and length just seems right.
>> Thanks for getting this right.

>>> I like your idea to use the length as array index - that way no sorting
>>> is needed. Brilliant!
>> Well that is grossly exaggerated, but thank you anyway, the idea was
>> yours of course I just used an Array instead of a Hash, that must be
>> my tiny Lua background.

>> Cheers
>> Robert

> I wondered whether these rather convoluted solutions had the
> redeeming feature of being faster than a simple and natural
> solution. It turned out that they are slower:

> user system total real
> simple 0.671000 0.010000 0.681000 ( 0.711000)
> dober1 1.472000 0.010000 1.482000 ( 1.512000)
> klemme1 1.261000 0.000000 1.261000 ( 1.292000)
> klemme2 2.324000 0.010000 2.334000 ( 2.363000)
> dober2 1.903000 0.000000 1.903000 ( 1.963000)
> klemme3 1.211000 0.000000 1.211000 ( 1.242000)
> martin 1.663000 0.000000 1.663000 ( 1.692000)

> Here's the benchmark code:

> require 'benchmark'

> # Find longest strings.
> class Array
> def simple
> max = map{|s| s.size}.max
> select{|s| s.size == max}
> end

> def dober1
> inject(){ |s, e|
> if s.empty? || s.first.size < e.to_s.size then [e]
> elsif s.first.size == e.to_s.size then s << e
> else s
> end
> }
> end

> def klemme1
> inject() do |lg, e|
> case
> when lg.empty?, lg.first.size == e.size
> lg << e
> when lg.first.size < e.size
> [e]
> else
> lg
> end
> end
> end

> def klemme2
> lg = Hash.new {|h,k| h[k] = }
> each {|x| lg[x.size] << x}
> lg.sort_by {|k,v| k}.last.pop
> end

> def dober2
> inject(){|a,e| a[e.size] = (a[e.size]||)+[e]
> a}.last
> end

> def klemme3
> inject(){|a,e|(a[e.size]||=)<<e;a}.last
> end

> def martin
> inject([0,]) { |(s,l),e|
> if e.to_s.size == s then [s,l << e]
> elsif e.to_s.size < s then [s,l]
> else [e.to_s.size, [e]]
> end
> }[1]
> end

> end

> methods = %w(simple dober1
> klemme1 klemme2 dober2 klemme3 martin)

> words = %w(posit that it suffices that the past
> is exempt from mutation)

> # Verify that all methods produce same result.
> p methods.map{|method| words.send( method )}.uniq
> puts

> Benchmark.bm( 7 ) do |bm|

> methods.each{|method|
> bm.report( method ) do
> 10_000.times{ words.send( method ) }
> end
> }

> end

Actually you forgot an an even simpler solution - although it is not as
short as the other ones. Probably better call it "more straightforward":

   def simple2
     res =
     len = 0

     each do |s|
       case s.length <=> len
         when 1
           len = s.length
           res.clear << s
         when 0
           res << s
         when -1
           # too short, ignore
         else
           raise "Wrong comparison result: #{s.length <=> len}"
       end
     end

     res
   end

13:00:10 [Temp]: ./maxing.rb
[["suffices", "mutation"]]

              user system total real
simple 0.235000 0.000000 0.235000 ( 0.239000)
simple2 0.219000 0.000000 0.219000 ( 0.187000)
dober1 0.437000 0.000000 0.437000 ( 0.435000)
klemme1 0.407000 0.000000 0.407000 ( 0.401000)
klemme2 0.734000 0.000000 0.734000 ( 0.765000)
dober2 0.656000 0.000000 0.656000 ( 0.658000)
klemme3 0.344000 0.000000 0.344000 ( 0.376000)
martin 0.531000 0.000000 0.531000 ( 0.529000)

Although this is faster, it would take me a lot longer,
starting from scratch, to understand it than to understand
the 2-line "simple" method.

The reason is - of course - that you need to traverse the array only once.

Btw, any idea why I'm seeing this if I replace "bm" with "bmbm"?

13:00:44 [Temp]: ./maxing.rb
[["suffices", "mutation"]]

Rehearsal -------------------------------------------
simple ./maxing.rb:99:in `send': undefined method `simple ' for
#<Array:0x104226b4> (NoMethodError)
         from ./maxing.rb:99
         from ./maxing.rb:99:in `times'
         from ./maxing.rb:99
         from /usr/lib/ruby/1.8/benchmark.rb:293:in `measure'
         from /usr/lib/ruby/1.8/benchmark.rb:261:in `bmbm'
         from /usr/lib/ruby/1.8/benchmark.rb:259:in `each'
         from /usr/lib/ruby/1.8/benchmark.rb:259:in `bmbm'
         from ./maxing.rb:95

Even though "simple" is reported an instance method of Arrray?

I noticed the same thing during my testing.
Benchmark tacks a space onto the "method" string.
A simple fix:
      10_000.times{ words.send( method.strip ) }

···

On May 2, 6:05 am, Robert Klemme <shortcut...@googlemail.com> wrote:

On 02.05.2007 02:50, William James wrote:
> On May 1, 2:26 am, "Robert Dober" <robert.do...@gmail.com> wrote:
>> On 5/1/07, Robert Klemme <shortcut...@googlemail.com> wrote:
>>> On 01.05.2007 00:03, Robert Dober wrote:
>>>> On 4/30/07, Robert Klemme <shortcut...@googlemail.com> wrote:

Kind regards

        robert

Similar to Robert Klemme's above:

module Enumerable
  def longest
    group_by { |a| a.to_s.size }.max.last
  end
end

···

--
Posted via http://www.ruby-forum.com/.