Mini-RCR: Extra Argument for Array#join


#1

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 G. II


#2

On 12/31/06, James Edward G. II removed_email_address@domain.invalid 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.


#3

James Edward G. 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.)


#4

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


#5

On Dec 31, 2006, at 12:05 PM, James Edward G. 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)


#6

On Dec 31, 2006, at 4:16 PM, Ryan D. 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 G. II


#7

On 12/31/06, Devin M. removed_email_address@domain.invalid 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.


#8

Ryan D. 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.


#9

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


#10

James G. 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.


#11

Ryan D. 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.


#12

On 1/3/07, Trans removed_email_address@domain.invalid 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 :wink:

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

#13

Robert D. 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


#14

I got so taken away by optimizing, I missed my point!
I feel that #join should be left alone, if a more powerful
String# is to be needed maybe it would be better to call it
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 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

#15

Robert D. 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,

:slight_smile:

but as we are looking for performance :wink:

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.


#16

On 1/3/07, Trans removed_email_address@domain.invalid wrote:

:slight_smile:
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 :slight_smile: 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

#17

On 1/3/07, Devin M. removed_email_address@domain.invalid wrote:

Robert D. 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 :wink:

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? :wink:

“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