human-readable listing of array elements

A

Aldric Giacomoni

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(tm)" 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
 
R

Robert Klemme

2009/12/10 Aldric Giacomoni said:
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(tm)" to write
this?

def stringify array
=A0return array[0] if array.size =3D=3D 1
=A0string =3D ""
=A0array.each_with_index do |element, index|
=A0 =A0case index
=A0 =A0 =A0when array.size - 1 # last item
=A0 =A0 =A0 =A0string +=3D " and #{element}"
=A0 =A0 =A0when 0
=A0 =A0 =A0 =A0string +=3D "#{element}"
=A0 =A0 =A0else
=A0 =A0 =A0 =A0string +=3D ", #{element}"
=A0 =A0end
=A0end
=A0string
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 =3D 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
=3D> nil
irb(main):031:0> stringify []
=3D> ""
irb(main):032:0> stringify %w{foo}
=3D> "foo"
irb(main):033:0> stringify %w{foo bar}
=3D> "foo and bar"
irb(main):034:0> stringify %w{foo bar baz}
=3D> "foo, bar and baz"


irb(main):039:0> def stringify array
irb(main):040:1> tail =3D 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 []
=3D> ""
irb(main):045:0> stringify %w{foo}
=3D> "foo"
irb(main):046:0> stringify %w{foo bar}
=3D> "foo and bar"
irb(main):047:0> stringify %w{foo bar baz}
=3D> "foo, bar and baz"

Note, I don't claim that this is necessarily more rubyish. :)

Kind regards

robert


--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
A

Aldric Giacomoni

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

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. :)

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!)
 
M

Marnen Laibow-Koser

Aldric said:
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(tm)" 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

Best,
-- 
Marnen Laibow-Koser
http://www.marnen.org
(e-mail address removed)
 
R

Robert Klemme

2009/12/10 Aldric Giacomoni said:
Robert said:
By using String#<< your code will become more efficient because you do
not create new String objects all the time. Thanks :)

For the fun of it here are two other solutions:

irb(main):026:0> def stringify array
irb(main):027:1> =A0 lead =3D array.slice 0...-1
irb(main):028:1> =A0 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> =A0 tail =3D array.slice -2..-1
irb(main):041:1> =A0 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. :)

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. :)
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. :)

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
A

Aldric Giacomoni

Marnen said:
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 :)
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.
 
M

Marnen Laibow-Koser

Aldric said:
Marnen said:
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 :)
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
(e-mail address removed)
 
A

Aldric Giacomoni

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

RichardOnRails

2009/12/10 Aldric Giacomoni <[email protected]>:


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(tm)" 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

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. :)

Kind regards

robert

Hey Robert,
Note, I don't claim that this is necessarily more rubyish. :)

Maybe not, but I call it very clever.

Best wishes,
Richard
 
M

Marnen Laibow-Koser

Aldric said:
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
(e-mail address removed)
 
R

Robert Klemme

2009/12/10 Aldric Giacomoni said:
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 =3D lambda {|n| n <=3D 1}
=3D> #<Proc:0x1014ed90@(irb):1 (lambda)>
irb(main):002:0> class <<ONE_OR_LESS; alias =3D=3D=3D []; end
=3D> 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
=3D> 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 # <=3D 1
...
when
...
end

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
B

Benoit Daloze

[Note: parts of this message were removed to make it a legal post.]

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 Klemme said:
2009/12/10 Aldric Giacomoni said:
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
 
A

Aldric Giacomoni

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

Marnen Laibow-Koser

Benoit said:
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. :)
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 Klemme <[email protected]>

Best,
-- 
Marnen Laibow-Koser
http://www.marnen.org
(e-mail address removed)
 
R

Robert Klemme

Benoit Daloze wrote:
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.

Me, too.
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. :)

Not too difficult either, let's see....

Kind regards

robert
 
R

Robert Klemme

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.

My comment referred to the "won't handle <=" part. I demonstrated how
it can be made to handle less than comparisons directly, i.e. without
using ranges which is not exactly the same operation.

Cheers

robert
 

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,982
Messages
2,570,185
Members
46,738
Latest member
JinaMacvit

Latest Threads

Top