Receiving array naturally?

As I learn Ruby, I find a lot of flexibility in the syntax. I was
thinking there was a way to do something like so: Say I had a method
that would receive an array. Is it possible to define the method so that
it can take the array elements /either/ as an Array object, /or/ as a
comma-separated (variable) list of elements (without the brackets)?

Like,

  process_data(string1, string2, string3, ...)

would be the same as

  string_array = [string1, string2, string3, ...]
  process_data(string_array)

I thought something like so would work:

  def process_data(*strings)
     ...
  end

That works with the comma-separated list, because the list gets combined
into one array ("strings"), but if I pass in an array object, that array
object is not split up, but rather becomes one (Array object) element of
the "strings" array.

Do I need to write code that checks whether each object passed in is an
array or not, manually splitting if necessary (which perhaps is
complicated) or is there something else I'm overlooking? Or perhaps is
my whole idea dumb from the start...?

···

--
Posted via http://www.ruby-forum.com/.

If in the caller you know whether it's an array or not you can use the
splat when calling the method:

array = %w{a b c d e}
process_data(*array)

keeping the definition as
def process_data(*strings)

Don't know if this is good enough for your use case or not.

Jesus.

···

On Wed, Sep 8, 2010 at 7:10 AM, Terry Michaels <spare@frigidcode.com> wrote:

As I learn Ruby, I find a lot of flexibility in the syntax. I was
thinking there was a way to do something like so: Say I had a method
that would receive an array. Is it possible to define the method so that
it can take the array elements /either/ as an Array object, /or/ as a
comma-separated (variable) list of elements (without the brackets)?

Like,

process_data(string1, string2, string3, ...)

would be the same as

string_array = [string1, string2, string3, ...]
process_data(string_array)

I thought something like so would work:

def process_data(*strings)
...
end

That works with the comma-separated list, because the list gets combined
into one array ("strings"), but if I pass in an array object, that array
object is not split up, but rather becomes one (Array object) element of
the "strings" array.

Do I need to write code that checks whether each object passed in is an
array or not, manually splitting if necessary (which perhaps is
complicated) or is there something else I'm overlooking? Or perhaps is
my whole idea dumb from the start...?

I wouldn't judge on this one although I generally believe that
reduction of alternatives increases clarity.

You could do

def simple(*a)
  a.flatten.each do |x|
    x.whatever
  end
end

def complex(*a)
  if a.length == 1 && Enumerable === a.first
    a.first
  else
    a
  end.each do |x|
    x.whatever
  end
end

Kind regards

robert

···

On Wed, Sep 8, 2010 at 7:10 AM, Terry Michaels <spare@frigidcode.com> wrote:

As I learn Ruby, I find a lot of flexibility in the syntax. I was
thinking there was a way to do something like so: Say I had a method
that would receive an array. Is it possible to define the method so that
it can take the array elements /either/ as an Array object, /or/ as a
comma-separated (variable) list of elements (without the brackets)?

Like,

process_data(string1, string2, string3, ...)

would be the same as

string_array = [string1, string2, string3, ...]
process_data(string_array)

I thought something like so would work:

def process_data(*strings)
...
end

That works with the comma-separated list, because the list gets combined
into one array ("strings"), but if I pass in an array object, that array
object is not split up, but rather becomes one (Array object) element of
the "strings" array.

Do I need to write code that checks whether each object passed in is an
array or not, manually splitting if necessary (which perhaps is
complicated) or is there something else I'm overlooking? Or perhaps is
my whole idea dumb from the start...?

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

Terry Michaels wrote:

  def process_data(*strings)
     ...
  end

That works with the comma-separated list, because the list gets combined
into one array ("strings"), but if I pass in an array object, that array
object is not split up, but rather becomes one (Array object) element of
the "strings" array.

is there something else I'm overlooking?

You've overlooked the simplest way:

def process_data(*strings)
  Array(strings).each do |s|
    ...
  end
end

def process_data(*strings)
...
end

yes

Do I need to write code that checks whether each object passed in is an array or not

yes

, manually splitting if necessary (which perhaps is
complicated) or is there something else I'm overlooking? Or perhaps is
my whole idea dumb from the start...?

no

starting fr your splat technique...
you just need to adjust a little bit...

eg,

def m *a
  a = a.first if a.first.is_a? Array
  a
end

=> nil

m 1,2

=> [1, 2]

m [1,2]

=> [1, 2]

m [1,2,"a",[3,4]]

=> [1, 2, "a", [3, 4]]

m 1,2,"a",[3,4]

=> [1, 2, "a", [3, 4]]

welcome to the wonderful world of ruby.
best regards -botp

···

On Wed, Sep 8, 2010 at 1:10 PM, Terry Michaels <spare@frigidcode.com> wrote:

You could do

def simple(*a)
a.flatten.each do |x|
x.whatever
end
end

It's also possible to use * when passing the array to the method,
without changing the method:

some_items = [1, "more", %w{four six eight}]
some_method *some_items

Regards,
Ammar

···

On Wed, Sep 8, 2010 at 10:09 AM, Robert Klemme <shortcutter@googlemail.com> wrote:

Clifford Heath wrote:

Terry Michaels wrote:

  def process_data(*strings)
     ...
  end

That works with the comma-separated list, because the list gets combined
into one array ("strings"), but if I pass in an array object, that array
object is not split up, but rather becomes one (Array object) element of
the "strings" array.

is there something else I'm overlooking?

You've overlooked the simplest way:

def process_data(*strings)
  Array(strings).each do |s|
    ...
  end
end

That doesn't work, because an array passed it gets treated like simply
an element of the "strings" array:

def process_data(*strings)
  Array(strings).each do |s|
    print s.class.to_s + " " + s.to_s + " " + "\n"
  end
end

process_data("a", "b", "c")
process_data(["a", "b", "c"])

# Produces:

···

#
# String a
# String b
# String c
# Array abc
--
Posted via http://www.ruby-forum.com/\.

Of course, but that's not what Terry asked for. He wanted to make it
convenient for the caller so he could do

m(1,2,3,4)
m([1,2,3,4])
m(some_array)

Kind regards

robert

···

On Wed, Sep 8, 2010 at 11:36 AM, Ammar Ali <ammarabuali@gmail.com> wrote:

On Wed, Sep 8, 2010 at 10:09 AM, Robert Klemme > <shortcutter@googlemail.com> wrote:

You could do

def simple(*a)
a.flatten.each do |x|
x.whatever
end
end

It's also possible to use * when passing the array to the method,
without changing the method:

some_items = [1, "more", %w{four six eight}]
some_method *some_items

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

Terry Michaels wrote:

Clifford Heath wrote:

You've overlooked the simplest way:

That doesn't work, because an array passed it gets treated like simply an element of the "strings" array:

Ah, right you are. I was thinking of this:

Array([2]) => [2]
Array(2) => [2]
but Array(*[1,2,3]) => ArgumentError: wrong number of arguments (3 for 1)

... and yes, I left out the *.
What I was thinking of was Array[*strings]

However (I knew I'd done something like this before!):

def process_data(*strings)
  strings = *strings
  strings.each do |s|
    print s.class.to_s + " " + s.to_s + " " + "\n"
  end
end

process_data("a", "b", "c")
process_data(["a", "b", "c"])

Does what you want.

Clifford Heath.

Clifford Heath wrote:

Terry Michaels wrote:

Clifford Heath wrote:

You've overlooked the simplest way:

That doesn't work, because an array passed it gets treated like simply
an element of the "strings" array:

Ah, right you are. I was thinking of this:

Array([2]) => [2]
Array(2) => [2]
but Array(*[1,2,3]) => ArgumentError: wrong number of arguments (3 for
1)

... and yes, I left out the *.
What I was thinking of was Array[*strings]

However (I knew I'd done something like this before!):

def process_data(*strings)
  strings = *strings
  strings.each do |s|
    print s.class.to_s + " " + s.to_s + " " + "\n"
  end
end

process_data("a", "b", "c")
process_data(["a", "b", "c"])

Does what you want.

Clifford Heath.

This seems to work.

If I might ask, what exactly is the line

  strings = *strings

doing?

For that matter, I could use a little more clarification on what the '*'
(glob?) operator does...

···

--
Posted via http://www.ruby-forum.com/\.

Terry Michaels wrote:

If I might ask, what exactly is the line
  strings = *strings
doing?
For that matter, I could use a little more clarification on what the '*' (glob?) operator does...

It's not really a normal operator, rather it's part of Ruby's syntax
that happens to look like an operator, and in some cases operates like
one. It's often called the splat operator because it looks like a
squashed bug ;-). It means different things in different contexts.

Preceding the last parameter in a method's parameter list (excluding &,
which is also special), it says that any parameters passed in addition
to any previously declared, should be passed as an array to this parameter.
This is how Ruby handles methods with a variable number of parameters.

In an assignment statement, it says "expand this array and do a multi
assignment". If the value is not an array, it continues to act like a
single assignment... and that's the magic I'm using in my example.

On the left-hand-side, this multi-assignment has only one receiver, which
again is a special case, like the reverse of splat. It joins all the
values being assigned into an array and assigns that value. So,

strings = *strings

is a way to array-ise something that might or might not be an array.

Just a final note on multi-assignment, if there is a comma-separated
list on the left, possibly ending with a comma, only the respective
values are assigned and the rest are discarded. So these are equivalent:

a, = *strings
a = strings[0]

and so are these:

a, b = *strings
a, b = strings[0], strings[1]

The final thing you might want to do is:

a, b, *c = *strings

Umm, try it, you'll figure out what it's doing. Like calling a splat method.

This is just a magic part of Ruby's syntax, mostly useful for confusing
newbies and making your code unreadable except by wizards ;-).

Clifford Heath.

Terry Michaels wrote:

If I might ask, what exactly is the line
strings = *strings
doing?
For that matter, I could use a little more clarification on what the '*'
(glob?) operator does...

It's not really a normal operator, rather it's part of Ruby's syntax
that happens to look like an operator, and in some cases operates like
one. It's often called the splat operator because it looks like a
squashed bug ;-). It means different things in different contexts. ...

That's a useful summary of assignment, which might well be worth putting
somewhere permanent.

This is just a magic part of Ruby's syntax, mostly useful for confusing
newbies and making your code unreadable except by wizards ;-).

Unless you put a small comment explaining the code, but that defeats the
object of the last part of that sentence! :slight_smile:

···

On Fri, Sep 10, 2010 at 7:10 AM, Clifford Heath <no@spam.please.net> wrote:

Clifford Heath wrote:

This is just a magic part of Ruby's syntax, mostly useful for confusing
newbies and making your code unreadable except by wizards ;-).

Clifford Heath.

That's pretty cool. Thanks for the good explanation.

···

--
Posted via http://www.ruby-forum.com/\.

Hi --

Terry Michaels wrote:

If I might ask, what exactly is the line
  strings = *strings
doing?
For that matter, I could use a little more clarification on what the '*' (glob?) operator does...

It's not really a normal operator, rather it's part of Ruby's syntax
that happens to look like an operator, and in some cases operates like
one.

Wait... can you operate but not be an operator? :slight_smile:

Just a final note on multi-assignment, if there is a comma-separated
list on the left, possibly ending with a comma, only the respective
values are assigned and the rest are discarded. So these are equivalent:

a, = *strings
a = strings[0]

and so are these:

a, b = *strings
a, b = strings[0], strings[1]

The final thing you might want to do is:

a, b, *c = *strings

You don't need the * on strings in any of those, though:

   >> strings = %w{ one two three }
   => ["one", "two", "three"]
   >> a, = *strings; a
   => "one"
   >> a, = strings; a
   => "one"

   >> a,b = *strings; [a,b]
   => ["one", "two"]
   >> a,b = strings; [a,b]
   => ["one", "two"]

   >> a,b,*c = *strings; [a,b,c]
   => ["one", "two", ["three"]]
   >> a,b,*c = strings; [a,b,c]
   => ["one", "two", ["three"]]

Those all work the same in 1.8. and 1.9, though 1.8 and 1.9 do some
parallel assignment things somewhat differently from each other. Here's
1.8:

   >> *a = [1,2,3]; a
   => [[1, 2, 3]]

and 1.9:

   >> *a = [1,2,3]; a
   => [1, 2, 3]

David

···

On Fri, 10 Sep 2010, Clifford Heath wrote:

--
David A. Black, Senior Developer, Cyrus Innovation Inc.

   The Ruby training with Black/Brown/McAnally
   Compleat Philadelphia, PA, October 1-2, 2010
   Rubyist http://www.compleatrubyist.com