Mini-RCR: Extra Argument for Array#join

  • Thread starter James Edward Gray II
  • Start date
J

James Edward Gray II

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 :eek:ld_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
 
G

Gregory Brown

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.
 
P

Phrogz

James said:
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.)
 
D

Devin Mullins

Phrogz said:
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
 
G

Gregory Brown

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.
 
R

Ryan Davis

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 :eek:ld_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

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 :eek:ld_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)
 
J

James Edward Gray II

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 :eek:ld_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

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
 
T

Trans

Ryan said:
class Array
alias_method :eek:ld_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)

I wonder how this would fair.

class Array
alias :eek:ld_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.
 
R

Robert James

James said:
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.
 
R

Ryan Davis

I wonder how this would fair.

class Array
alias :eek:ld_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.

% ./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
 
T

Trans

Ryan said:
% ./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

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

Devin Mullins

Robert said:
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
 
T

Trans

Robert said:
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.
 

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,968
Messages
2,570,153
Members
46,699
Latest member
AnneRosen

Latest Threads

Top