Forum: Ruby Mini-RCR: Extra Argument for Array#join

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2006-12-31 21:05
(Received via mailing list)
I've been looking at the to_sentence() method in Rails.  It's
basically a join(), but you can give a different final separator.  I
personally use this kind of functionality often enough to believe it
would make a good addition to the core language.

Even better, I think we can make join() smart enough to eliminate any
need for to_sentence() by adding a second argument.  Here's a sample
implementation:

#!/usr/bin/env ruby -w

require "enumerator"

class Array
   alias_method :old_join, :join
   def join(sep = $,, last_sep = sep)
     return "" if empty?
     enum_with_index.inject("") do |str, (e, i)|
       "#{str}#{i == size - 1 ? last_sep : sep}#{e}"
     end[sep.to_s.length..-1]
   end
end

if __FILE__ == $PROGRAM_NAME
   require "test/unit"

   class TestJoin < Test::Unit::TestCase
     def test_old_matches_new
       assert_equal([].old_join,          [].join)
       assert_equal([1].old_join,         [1].join)
       assert_equal([1, 2].old_join,      [1, 2].join)
       assert_equal((1..5).to_a.old_join, (1..5).to_a.join)

       assert_equal([].old_join("|"),          [].join("|"))
       assert_equal([1].old_join("|"),         [1].join("|"))
       assert_equal([1, 2].old_join("|"),      [1, 2].join("|"))
       assert_equal((1..5).to_a.old_join("|"), (1..5).to_a.join("|"))
     end

     def test_new_last_arg_behavior
       assert_equal("1, 2, 3, 4, and 5", (1..5).to_a.join(", ", ",
and "))
       assert_equal("1, 2, 3, 4 and 5",  (1..5).to_a.join(", ", " and
"))
       assert_equal("1, 2, 3, 4 & 5",    (1..5).to_a.join(", ", " & "))

       assert_equal([1, 2].join(","), [1, 2].join("ignored", ","))
     end
   end
end

__END__

Does anyone else like this?

James Edward Gray II
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2006-12-31 21:17
(Received via mailing list)
On 12/31/06, James Edward Gray II <james@grayproductions.net> wrote:

> Does anyone else like this?

It's neat.  I think it's better than to_sentence.

But maybe not something necessary for core.

I tend to think of join() to be a delimiter-generator, and not an
english language generator.

But that's just my two cents.  Wouldn't be opposed to an adjustment,
but personally have never needed it for my day to day Ruby usage.
852a62a28f1de229dc861ce903b07a60?d=identicon&s=25 Gavin Kistner (phrogz)
on 2006-12-31 21:25
(Received via mailing list)
James Edward Gray II wrote:
> I've been looking at the to_sentence() method in Rails.  It's
> basically a join(), but you can give a different final separator.  I
> personally use this kind of functionality often enough to believe it
> would make a good addition to the core language.

What makes the final separator so special that it deserves inclusion,
while making no allowance for a pre-separator? Just the English
language?

(At the same time as I ask this, I'm wondering if asking for
completeness for the sake of completeness, without a corresponding use
case, is the sort of foolish consistency that is the hobgoblin of
little minds.)
918c6daad03c85e51ad1a11f57017947?d=identicon&s=25 Devin Mullins (twifkak)
on 2006-12-31 22:31
(Received via mailing list)
Phrogz wrote:
> What makes the final separator so special that it deserves inclusion,
> while making no allowance for a pre-separator? Just the English
> language?

Well, just for poops and giggles, here's a less racist alternative
(JEG-compatible):

require "enumerator"

class Array
   alias old_join join
   def join(sep = $,, last_sep = nil, &each_prefix)
     each_prefix ||= proc do |e,i|
       (last_sep if i == length - 1) || sep
     end
     enum_with_index.map do |e,i|
       i == 0 ? e : "#{each_prefix[e,i]}#{e}"
     end.old_join
   end
end

if __FILE__ == $PROGRAM_NAME
   require "test/unit"

   class TestJoin < Test::Unit::TestCase
     def test_old_matches_new
       assert_equal([].old_join,          [].join)
       assert_equal([1].old_join,         [1].join)
       assert_equal([1, 2].old_join,      [1, 2].join)
       assert_equal((1..5).to_a.old_join, (1..5).to_a.join)

       assert_equal([].old_join("|"),          [].join("|"))
       assert_equal([1].old_join("|"),         [1].join("|"))
       assert_equal([1, 2].old_join("|"),      [1, 2].join("|"))
       assert_equal((1..5).to_a.old_join("|"), (1..5).to_a.join("|"))
     end

     def test_new_last_arg_behavior
       assert_equal("1, 2, 3, 4, and 5", (1..5).to_a.join(", ", ", and
"))
       assert_equal("1, 2, 3, 4 and 5",  (1..5).to_a.join(", ", " and
"))
       assert_equal("1, 2, 3, 4 & 5",    (1..5).to_a.join(", ", " & "))

       assert_equal([1, 2].join(","), [1, 2].join("ignored", ","))
     end

     def test_new_block_behavior
       assert_equal '1 dna, 2, 3, 4, 5',
                    (1..5).to_a.join {|e,i| i == 1 ? ' dna, ' : ', ' }
     end
   end
end
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2006-12-31 22:39
(Received via mailing list)
On 12/31/06, Devin Mullins <twifkak@comcast.net> wrote:

>      def test_new_block_behavior
>        assert_equal '1 dna, 2, 3, 4, 5',
>                     (1..5).to_a.join {|e,i| i == 1 ? ' dna, ' : ', ' }
>      end


Hmm... this seems powerful and generally useful.
5a837592409354297424994e8d62f722?d=identicon&s=25 Ryan Davis (Guest)
on 2006-12-31 23:18
(Received via mailing list)
On Dec 31, 2006, at 12:05 PM, James Edward Gray II wrote:

>
> end
problems:

1) calculating a static value inside a loop. (only costs about a
hundredth of a second for 10k)
2) iterating when 1/2 of your values are completely static.
3) being clever by using enumerator.

class Array
   alias_method :old_join, :join
   def join(sep = $,, last_sep = sep)
     return "" if empty?

     seperators = Array.new(size-1, sep)
     seperators[-1] = last_sep unless seperators.empty?
     self.zip(seperators).old_join
   end
end

# of iterations = 1000000
                           user     system      total        real
null_time             0.140000   0.000000   0.140000 (  0.140482)
jeg                  29.690000   0.080000  29.770000 ( 30.144816)
ryan                 19.780000   0.050000  19.830000 ( 19.998077)
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2006-12-31 23:24
(Received via mailing list)
On Dec 31, 2006, at 4:16 PM, Ryan Davis wrote:

>> sample implementation:
>>       "#{str}#{i == size - 1 ? last_sep : sep}#{e}"
>>     end[sep.to_s.length..-1]
>>   end
>> end
>
> problems:
>
> 1) calculating a static value inside a loop. (only costs about a
> hundredth of a second for 10k)
> 2) iterating when 1/2 of your values are completely static.
> 3) being clever by using enumerator.

This was intended as a point of discussion, not my final offer as the
ideal implementation.  Thank you for cleaning it up though.

James Edward Gray II
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-01-01 17:41
(Received via mailing list)
Ryan Davis wrote:
>
> # of iterations = 1000000
>                            user     system      total        real
> null_time             0.140000   0.000000   0.140000 (  0.140482)
> jeg                  29.690000   0.080000  29.770000 ( 30.144816)
> ryan                 19.780000   0.050000  19.830000 ( 19.998077)

I wonder how this would fair.

  class Array
    alias :old_join :join
    def join( sep=$,, last_sep=nil )
      s = old_join(sep)
      if last_sep
        rsep = Regexp.escape(sep.to_s)
        rlast = Regexp.escape(last.to_s)
        s.sub!(/#{rsep}#{rlast}$/,"#{last_sep}#{last}")
      end
      return s
    end
  end

Sorry, the bencmark script wasn't posted and I didn't feel like
recreating it.

T.
53a1f0aba6f0489d49f5e6fc3df323fa?d=identicon&s=25 Robert James (robertjames)
on 2007-01-02 02:12
James Gray wrote:
> I've been looking at the to_sentence() method in Rails.  It's
> basically a join(), but you can give a different final separator.  I
> personally use this kind of functionality often enough to believe it
> would make a good addition to the core language.

-1
join is simple to understand and use.  It's used in lots of things
besides English generation.  Adding this complicates it and confuses the
intention.

If you feel to_sentence() should be made core, put up a RCR for it.  But
let's keep join as join.
5a837592409354297424994e8d62f722?d=identicon&s=25 Ryan Davis (Guest)
on 2007-01-03 03:34
(Received via mailing list)
On Jan 1, 2007, at 8:40 AM, Trans wrote:

>       end
>       return s
>     end
>   end
>
> Sorry, the bencmark script wasn't posted and I didn't feel like
> recreating it.

% ./blah.rb 1_000_000
# of iterations = 1000000
                           user     system      total        real
null_time             0.140000   0.000000   0.140000 (  0.139480)
ryan                 19.760000   0.020000  19.780000 ( 19.810269)
trans                21.290000   0.040000  21.330000 ( 21.398592)
Loaded suite ./blah
Started
....
Finished in 0.001109 seconds.

4 tests, 24 assertions, 0 failures, 0 errors
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-01-03 04:45
(Received via mailing list)
Ryan Davis wrote:
> >         rlast = Regexp.escape(last.to_s)
> # of iterations = 1000000
>                            user     system      total        real
> null_time             0.140000   0.000000   0.140000 (  0.139480)
> ryan                 19.760000   0.020000  19.780000 ( 19.810269)
> trans                21.290000   0.040000  21.330000 ( 21.398592)
> Loaded suite ./blah
> Started
> ....
> Finished in 0.001109 seconds.
>
> 4 tests, 24 assertions, 0 failures, 0 errors

Eek. That was worse then I thought it would be. Thanks for showing me
though, Ryan.

Of course now that I'm looking at it again I'm wondering how we missed:

  class Array
    def join( sep=$,, last_sep=nil )
      return old_join(sep) unless last_sep
      [slice(0...-1).old_join(sep), last].old_join(last_sep)
    end
  end

That must to be faster.

T.
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-01-03 11:25
(Received via mailing list)
On 1/3/07, Trans <transfire@gmail.com> wrote:
> > >       s = old_join(sep)
> > > recreating it.
> > Finished in 0.001109 seconds.
>       return old_join(sep) unless last_sep
>       [slice(0...-1).old_join(sep), last].old_join(last_sep)


slice(0..-2).old_join(sep) << last_sep << last.to_s
or maybe
"#{slice(0..-2).old_join(sep)}#{last_sep}#{last}"

I like your approach of using old_join again, it seems so abstraction
based,
but as we are looking for performance ;)

    end
>   end
>
> That must to be faster.
>
> T.
>
>
>
Robert

--
"The real romance is out ahead and yet to come. The computer revolution
hasn't started yet. Don't be misled by the enormous flow of money into
bad
defacto standards for unsophisticated buyers using poor adaptations of
incomplete ideas."

- Alan Kay
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-01-03 12:12
(Received via mailing list)
I got so taken away by optimizing, I missed my point!
I feel that #join should be left alone, if a more powerful
String#<something> is to be needed maybe it would be better to call it
<something> than I feel too that having a prefix or postfix join
available.
A *strange* syntax would be possible (that's why it is not good, but I
love
it)
def <something> sep=$,, joins={}

list = %{Something blue smth new smth borrowed}
list.something( ', ' -2 => "and" )  => "Something, blue, smth, new, smth
and
borrowed"
list.something( ', ' 0 => "{", -1 => "}" ) => "{Something, blue, smth,
new,
smth, borrowed}"

now that is nonesense of course and might not work in 1.9/2.0???
let us be reasonable

list.something(:sep => ", ", :odd => "")  => "Something blue, smth new,
smth
borrowed"
list.something(:sep => ", ", :odd => "", :last_even => "and" ) =>
              "Something blue, smth new and smth borrowed"
and in full form we would have
(0..9).something( :pre => "<", :suff => ">", :even => "_",  :odd => "-",
:index => [5, "*"] ) =>
             "<0-1_2-3_4*5_6-7_8-9>"

That all still might be nonsense/bloat but I have gone to extremes to
show
why I think it would not be String#join anymore and thus should not be
called String#join

If OTOH a majority thinks enhancing String#join, what about a block
enhancement

list.join do
   |ele, index|
   (index % 2).zero? ? : "," ? "*" # might be nice to change sep on
value of
ele too.
end

cheers
Robert
--
"The real romance is out ahead and yet to come. The computer revolution
hasn't started yet. Don't be misled by the enormous flow of money into
bad
defacto standards for unsophisticated buyers using poor adaptations of
incomplete ideas."

- Alan Kay
918c6daad03c85e51ad1a11f57017947?d=identicon&s=25 Devin Mullins (twifkak)
on 2007-01-03 16:08
(Received via mailing list)
Robert Dober wrote:
> If OTOH a majority thinks enhancing String#join, what about a block
> enhancement
Dude... read the whole thread. You're not killfiling me, too, are you?

Not that I'm a fan of the block form... if join gets augmented, it
should be something fairly simple and 80/20, such as last_sep.

Devin
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-01-03 16:16
(Received via mailing list)
On 1/3/07, Devin Mullins <twifkak@comcast.net> wrote:
>
> Robert Dober wrote:
> > If OTOH a majority thinks enhancing String#join, what about a block
> > enhancement
> Dude... read the whole thread. You're not killfiling me, too, are you?


No I am not, just got taken away by hacking Ruby, very nice that code of
yours ;)

Not that I'm a fan of the block form... if join gets augmented, it
> should be something fairly simple and 80/20, such as last_sep.
>
> Devin
>
>

Robert
BTW I was called Mr. Dober and Dude recently,  is there something wrong
with
my name? ;)
--
"The real romance is out ahead and yet to come. The computer revolution
hasn't started yet. Don't be misled by the enormous flow of money into
bad
defacto standards for unsophisticated buyers using poor adaptations of
incomplete ideas."

- Alan Kay
45196398e9685000d195ec626d477f0e?d=identicon&s=25 Trans (Guest)
on 2007-01-03 17:31
(Received via mailing list)
Robert Dober wrote:
> slice(0..-2).old_join(sep) << last_sep << last.to_s
> or maybe
> "#{slice(0..-2).old_join(sep)}#{last_sep}#{last}"
>
> I like your approach of using old_join again, it seems so abstraction based,

:-)

> but as we are looking for performance ;)

Yep, even better.

I'll credit you and add this to Facets, but I'd rather name it
something other than #join. Right now I have #join_sentence, but that's
rather long. Any suggestions?

And for what it's worth, IMO a block additon to join certainly couldn't
hurt. In fact it would be quite nice for alternating separators.

  hash.to_a.flatten.join{ |index| index % 2 == 0 ? ':' : "\n" }

Or something like that.

T.
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-01-03 17:49
(Received via mailing list)
On 1/3/07, Trans <transfire@gmail.com> wrote:
> :-)
> hurt. In fact it would be quite nice for alternating separators.
>
>   hash.to_a.flatten.join{ |index| index % 2 == 0 ? ':' : "\n" }
>
> Or something like that.


I am flattered , thx :) concerning the block  I have to admit that Devin
has
come up with that idea early in the thread already!
Cheers
Robert

T.
>
>
>

--
"The real romance is out ahead and yet to come. The computer revolution
hasn't started yet. Don't be misled by the enormous flow of money into
bad
defacto standards for unsophisticated buyers using poor adaptations of
incomplete ideas."

- Alan Kay
This topic is locked and can not be replied to.