Human-readable listing of array elements

This took me less than a minute to write, but I don’t know if it’s as
elegant as it could be. Are there more “Ruby-like ways™” to write
this?

def stringify array
return array[0] if array.size == 1
string = “”
array.each_with_index do |element, index|
case index
when array.size - 1 # last item
string += " and #{element}"
when 0
string += “#{element}”
else
string += “, #{element}”
end
end
string
end

2009/12/10 Aldric G. [email protected]:

   string += " and #{element}"
 when 0
   string += "#{element}"
 else
   string += ", #{element}"

end
end
string
end

By using String#<< your code will become more efficient because you do
not create new String objects all the time.

For the fun of it here are two other solutions:

irb(main):026:0> def stringify array
irb(main):027:1> lead = array.slice 0…-1
irb(main):028:1> lead.empty? ? array.last.to_s : [lead.join(", “),
array.last].join(” and ")
irb(main):029:1> end
=> nil
irb(main):031:0> stringify []
=> “”
irb(main):032:0> stringify %w{foo}
=> “foo”
irb(main):033:0> stringify %w{foo bar}
=> “foo and bar”
irb(main):034:0> stringify %w{foo bar baz}
=> “foo, bar and baz”

irb(main):039:0> def stringify array
irb(main):040:1> tail = array.slice -2…-1
irb(main):041:1> tail ? (array.slice(0…-2) << tail.join(" and
“)).join(”, ") : array.first.to_s
irb(main):042:1> end

irb(main):048:0> stringify []
=> “”
irb(main):045:0> stringify %w{foo}
=> “foo”
irb(main):046:0> stringify %w{foo bar}
=> “foo and bar”
irb(main):047:0> stringify %w{foo bar baz}
=> “foo, bar and baz”

Note, I don’t claim that this is necessarily more rubyish. :slight_smile:

Kind regards

robert

Robert K. wrote:

By using String#<< your code will become more efficient because you do
not create new String objects all the time.
Thanks :slight_smile:

For the fun of it here are two other solutions:

irb(main):026:0> def stringify array
irb(main):027:1> lead = array.slice 0…-1
irb(main):028:1> lead.empty? ? array.last.to_s : [lead.join(", “),
array.last].join(” and ")
irb(main):029:1> end

irb(main):039:0> def stringify array
irb(main):040:1> tail = array.slice -2…-1
irb(main):041:1> tail ? (array.slice(0…-2) << tail.join(" and
“)).join(”, ") : array.first.to_s
irb(main):042:1> end

Note, I don’t claim that this is necessarily more rubyish. :slight_smile:

Your solutions stink of LISP, but maybe that’s only because you’re …
er… processing lists.
I like your first one - it feels more elegant than mine. The second one
feels a little too convoluted. I also find that using ‘slice’ is too
wordy, and usually just settle for array[0…-1] or array[-2…-1] – but
clearly that’s just preference. Thanks for the further examples, I’ll
remember them (and possibly even use them someday!)

2009/12/10 Aldric G. [email protected]:

array.last].join(" and ")
Your solutions stink of LISP, but maybe that’s only because you’re …
er… processing lists.

Oh no. Pascal is responsible for lispish solutions - I’m not. :slight_smile:

I like your first one - it feels more elegant than mine. The second one
feels a little too convoluted.

Yep.

I also find that using ‘slice’ is too
wordy, and usually just settle for array[0…-1] or array[-2…-1] – but
clearly that’s just preference.

I just felt #slice deserved a mention because it is so rarely seen.
Initially I also thought about using #slice! which cannot be done with
#[]. But then you would either modify the argument or have to dup the
Array.

Thanks for the further examples, I’ll
remember them (and possibly even use them someday!)

You’re welcome! Thanks for the opportunity to play around a bit. :slight_smile:

Kind regards

robert

Marnen Laibow-Koser wrote:

Interesting question.

class Array # or perhaps module Enumerable
def stringify(conjunction = ‘and’, separator = ', ')
if self.size <= 1
return self.to_s
elsif self.size == 2
return self.join " #{conjunction} "
else
array = self.dup
array[-1] = [conjunction, array[-1]].join ’ ’
array.join separator # proper English usage has comma before ‘and’
end
end
end

It is pretty, and (like Robert’s solution) handles the case where the
array has no elements. I wouldn’t put that into Enumerable though, not
sure how it would handle a hash, for instance :slight_smile:
Do you think if / elsif / else is better in this situation than “case
self.size”? What about in general?
I know it’s usually accepted that instead of several elsifs, one should
use ‘case’, but I usually try to avoid ‘elsif’ altogether.

Aldric G. wrote:

Marnen Laibow-Koser wrote:

Interesting question.

class Array # or perhaps module Enumerable
def stringify(conjunction = ‘and’, separator = ', ')
if self.size <= 1
return self.to_s
elsif self.size == 2
return self.join " #{conjunction} "
else
array = self.dup
array[-1] = [conjunction, array[-1]].join ’ ’
array.join separator # proper English usage has comma before ‘and’
end
end
end

It is pretty, and (like Robert’s solution) handles the case where the
array has no elements. I wouldn’t put that into Enumerable though, not
sure how it would handle a hash, for instance :slight_smile:
Do you think if / elsif / else is better in this situation than “case
self.size”?

Yes. case won’t handle <= conditions AFAIK.

What about in general?
I know it’s usually accepted that instead of several elsifs, one should
use ‘case’, but I usually try to avoid ‘elsif’ altogether.

Use whichever syntax is better suited to what you need. There’s nothing
wrong with elsif IMHO.

Best,
–Â
Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Marnen Laibow-Koser wrote:

Yes. case won’t handle <= conditions AFAIK.

That is a very good point, though it does handle ranges, so “case 0…1”
would fit this particular scenario.
Your elsif statement is probably better suited here though.

Aldric G. wrote:

Marnen Laibow-Koser wrote:

Yes. case won’t handle <= conditions AFAIK.

That is a very good point, though it does handle ranges, so “case 0…1”
would fit this particular scenario.
Your elsif statement is probably better suited here though.

If I’d remembered your point about ranges, I might have used case.

Best,
–Â
Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Aldric G. wrote:

This took me less than a minute to write, but I don’t know if it’s as
elegant as it could be. Are there more “Ruby-like ways™” to write
this?

array.inspect

Of course, that won’t get you the “and”.

def stringify array
return array[0] if array.size == 1
string = “”
array.each_with_index do |element, index|
case index
when array.size - 1 # last item
string += " and #{element}"
when 0
string += “#{element}”
else
string += “, #{element}”
end
end
string

Interesting question.

class Array # or perhaps module Enumerable
def stringify(conjunction = ‘and’, separator = ', ')
if self.size <= 1
return self.to_s
elsif self.size == 2
return self.join " #{conjunction} "
else
array = self.dup
array[-1] = [conjunction, array[-1]].join ’ ’
array.join separator # proper English usage has comma before ‘and’
end
end
end

end

Best,
–Â
Marnen Laibow-Koser
http://www.marnen.org
[email protected]

On Dec 10, 9:36 am, Robert K. [email protected] wrote:

string = “”
string
array.last].join(" and ")

irb(main):046:0> stringify %w{foo bar}

remember.guy do |as, often| as.you_can - without endhttp://blog.rubybestpractices.com/

Hey Robert,

Note, I don’t claim that this is necessarily more rubyish. :slight_smile:

Maybe not, but I call it very clever.

Best wishes,
Richard

2009/12/10 Aldric G. [email protected]:

Marnen Laibow-Koser wrote:

Yes. case won’t handle <= conditions AFAIK.

That is a very good point, though it does handle ranges, so “case 0…1”
would fit this particular scenario.
Your elsif statement is probably better suited here though.

That point is not exactly true: while it doesn’t out of the box, it
can easily be made to:

irb(main):001:0> ONE_OR_LESS = lambda {|n| n <= 1}
=> #<Proc:0x1014ed90@(irb):1 (lambda)>
irb(main):002:0> class <<ONE_OR_LESS; alias === []; end
=> nil
irb(main):003:0> 5.times {|i| case i; when ONE_OR_LESS then puts
“yow!” else puts “nah” end}
yow!
yow!
nah
nah
nah
=> 5
irb(main):004:0>

Apart from that, the length of an Array can as a minimum become only 0
so the same test can be done as

case array.size
when 0,1 # <= 1

when

end

Kind regards

robert

Robert K. wrote:

2009/12/10 Aldric G. [email protected]:

Marnen Laibow-Koser wrote:

Yes. �case won’t handle <= conditions AFAIK.

That is a very good point, though it does handle ranges, so “case 0…1”
would fit this particular scenario.
Your elsif statement is probably better suited here though.

That point is not exactly true: while it doesn’t out of the box…

c:>irb
irb(main):001:0> RUBY_VERSION
=> “1.8.7”
irb(main):002:0> 5.times { |a| case a; when 0…2 then puts “yow” else
puts “nah” end }
yow
yow
yow
nah
nah
=> 5
irb(main):003:0>

Alright… Well then, I’m confused.

Hi,

As written in “Programming Ruby 1.9”,
You can really use case as if … elsif … else … end

So, in our case:

if self.size <= 1
return self.to_s
elsif self.size == 2
return self.join " #{conjunction} "
else
array = self.dup
array[-1] = [conjunction, array[-1]].join ’ ’
array.join separator # proper English usage has comma before ‘and’
end

becomes:

case
when self.size <= 1
return self.to_s
when self.size == 2
return self.join " #{conjunction} "
else
array = self.dup
array[-1] = [conjunction, array[-1]].join ’ ’
array.join separator # proper English usage has comma before ‘and’
end

Well, that’s not especially better I think, but it looks cool and a
little
less ‘procedural’ to me.

Anyway, I’m wondering how case manage the comma(,), is it acting like a
OR
on each of the elements? I thought first to Array#=== but that isn’t
defined. So, is it sort of syntactic sugar ?

2009/12/10 Robert K. [email protected]

On 12/10/2009 06:49 PM, Marnen Laibow-Koser wrote:

Benoit D. wrote:

array = self.dup
return self.join " #{conjunction} "
else
array = self.dup
array[-1] = [conjunction, array[-1]].join ’ ’
array.join separator # proper English usage has comma before ‘and’
end

Thanks, I had forgotten about that too.

Me, too.

Well, that’s not especially better I think, but it looks cool and a
little
less ‘procedural’ to me.

It’s no less procedural, and in fact it uses another line of code for no
advantage that I can see.

The advantage for me is that “case” conveys a different notion: I am
reminded of SQL’s case which is an expression, i.e. you get a single
value out of several based on conditions after “where”. “if elsif else
end” is more about executing different procedural sequences. The
difference is less technical and more informal.

If you truly want this to look less procedural, I think you’d need to
somehow implement a polymorphic dispatch based on Array.size…but let’s
not go there. :slight_smile:

Not too difficult either, let’s see…

Kind regards

robert

Benoit D. wrote:

Hi,

As written in “Programming Ruby 1.9”,
You can really use case as if … elsif … else … end

So, in our case:

if self.size <= 1
return self.to_s
elsif self.size == 2
return self.join " #{conjunction} "
else
array = self.dup
array[-1] = [conjunction, array[-1]].join ’ ’
array.join separator # proper English usage has comma before ‘and’
end

becomes:

case
when self.size <= 1
return self.to_s
when self.size == 2
return self.join " #{conjunction} "
else
array = self.dup
array[-1] = [conjunction, array[-1]].join ’ ’
array.join separator # proper English usage has comma before ‘and’
end

Thanks, I had forgotten about that too.

Well, that’s not especially better I think, but it looks cool and a
little
less ‘procedural’ to me.

It’s no less procedural, and in fact it uses another line of code for no
advantage that I can see.

If you truly want this to look less procedural, I think you’d need to
somehow implement a polymorphic dispatch based on Array.size…but let’s
not go there. :slight_smile:

Anyway, I’m wondering how case manage the comma(,), is it acting like a
OR
on each of the elements? I thought first to Array#=== but that isn’t
defined. So, is it sort of syntactic sugar ?

2009/12/10 Robert K. [email protected]

Best,
–Â
Marnen Laibow-Koser
http://www.marnen.org
[email protected]

On Dec 10, 8:36 am, Robert K. [email protected] wrote:

case index

=> nil
irb(main):040:1> tail = array.slice -2…-1
irb(main):047:0> stringify %w{foo bar baz}
=> “foo, bar and baz”

Note, I don’t claim that this is necessarily more rubyish. :slight_smile:

Kind regards

robert


remember.guy do |as, often| as.you_can - without endhttp://blog.rubybestpractices.com/

Sometimes it’s fun to avoid using conditionals (including
“… ? … : …”)
when programming.

def to_string ary
a = ary.dup
a.push( a.pop(2).join " and " ).join ", "
end

On Thu, Dec 10, 2009 at 10:15 PM, Aldric G. [email protected]
wrote:

   string += " and #{element}"
 when 0
   string += "#{element}"
 else
   string += ", #{element}"

end
end
string
end

just joing the fun of ruby,

C:\prg>cat test3.rb

def stringify array
string = “”
array.each_with_index do |element, index|
string << case index
when 0
“”
when (as=array.size) - 1 # last item
(as < 3 ? “” : “,” ) + " and "
else
", "
end << “#{element}”
end
string
end

s=[]
%w(this is a test). each do |element|
p stringify(s<<[element])
end

C:\prg>ruby -W0 test3.rb
“this”
“this and is”
“this, is, and a”
“this, is, a, and test”

best regards -botp

2009/12/11 botp [email protected]:

 when array.size - 1 # last item
             ""

%w(this is a test). each do |element|
p stringify(s<<[element])
end

C:\prg>ruby -W0 test3.rb
“this”
“this and is”
“this, is, and a”
“this, is, a, and test”

The comma… :slight_smile:

Cheers

robert

2009/12/11 w_a_x_man [email protected]:

array.each_with_index do |element, index|
end
irb(main):029:1> end
irb(main):039:0> def stringify array
=> “foo and bar”
remember.guy do |as, often| as.you_can - without endhttp://blog.rubybestpractices.com/

Sometimes it’s fun to avoid using conditionals (including
“… ? … : …”)
when programming.

Here’s one way that completely gets rid of case and if. :slight_smile:

ALG = Hash.new( lambda {|a| [a[0…-1].join(", “), a[-1]].join(” and
")} ).merge(
0 => lambda {|a| “”},
1 => lambda {|a| a.first.to_s},
2 => lambda {|a| a.join " and "}
)

def s(a)
ALG[a.size][a]
end

arr = []

5.times do |i|
p arr, s(arr)
arr << “foo#{i}”
end

def to_string ary
a = ary.dup
a.push( a.pop(2).join " and " ).join ", "
end

Nice!

Cheers

robert

Aldric G.:

This took me less than a minute to write, but I don’t know if it’s as
elegant as it could be. Are there more “Ruby-like ways™” to write
this?

    string += ", #{element}"
end

end
string
end

Couldn’t resist:

def stringify a
a.size < 2 ? a.first.to_s : a[0…-2].join(’, ') + " and #{a.last}"
end

stringify []
=> “”

stringify [‘Arthur’]
=> “Arthur”

stringify [‘Arthur’, ‘Ford’]
=> “Arthur and Ford”

stringify [‘Arthur’, ‘Ford’, ‘Trillian’]
=> “Arthur, Ford and Trillian”

stringify [‘Arthur’, ‘Ford’, ‘Trillian’, ‘Marvin’]
=> “Arthur, Ford, Trillian and Marvin”

— Shot