Array#collect in a method call, not working for me

M

Michael Randall

I am sure I'm making a newbie mistake, as I've just started learning
Ruby, and would really appreciate someone pointing out what I've done
wrong, why it is wrong, and how it *should* be done. Thanks in advance.

I am writing a method definition to double any number, or all numbers if
an array of numbers is passed to it. When I use array.collect outside of
the method, it works as expected. When I place it inside of the method,
instead of multiplying each integer, it treats the entire array as one
object and double it, [1, 2, 3, 4] becoming 12341234.

I've attached my test code, "simplea.rb", and below is the output when I
run it. In case it matters to anyone, I'm running Ruby 1.8.7 under
Cygwin (because I hate windows and don't have a Mac yet). ;)

$ simplea.rb

Test double([1, 2, 3, 4])

12341234


Test double( 3 )

6



Let's prove it.
2
4
6
8

Attachments:
http://www.ruby-forum.com/attachment/4184/simplea.rb
 
H

Harry Kakueki

I am writing a method definition to double any number, or all numbers if
an array of numbers is passed to it. When I use array.collect outside of
the method, it works as expected. When I place it inside of the method,
instead of multiplying each integer, it treats the entire array as one
object and double it, [1, 2, 3, 4] becoming 12341234.

Using code similar to yours.......
You could try this or something like this.

def double(n)
[n].flatten.map {|num| num * 2 }
end

puts double([1, 2, 3, 4])
puts
puts double(3)


Harry
 
R

Robert Klemme

2009/10/26 Michael Randall said:
I am sure I'm making a newbie mistake, as I've just started learning
Ruby, and would really appreciate someone pointing out what I've done
wrong, why it is wrong, and how it *should* be done. Thanks in advance.

I am writing a method definition to double any number, or all numbers if
an array of numbers is passed to it. When I use array.collect outside of
the method, it works as expected. When I place it inside of the method,
instead of multiplying each integer, it treats the entire array as one
object and double it, [1, 2, 3, 4] becoming 12341234.

I've attached my test code, "simplea.rb", and below is the output when I
run it. In case it matters to anyone, I'm running Ruby 1.8.7 under
Cygwin (because I hate windows and don't have a Mac yet). ;)

$ simplea.rb

Test double([1, 2, 3, 4])

12341234


Test double( 3 )

6



Let's prove it.
2
4
6
8

Attachments:
http://www.ruby-forum.com/attachment/4184/simplea.rb

There are a few issues with the first line:

my_a = [ n ].to_a unless n.class == "Array"

First, the test will always fail because the class and the name of the
class are of different types:

irb(main):002:0> Array == "Array"
=> false

Then, you only need either "[n]" or "n.to_a" but not both. I
recommend the former (the latter is deprecated).

Here is _one_ way to do it:

def double(x)
Enumerable === x ? x.map {|i| i * 2} : x * 2
end

irb(main):006:0> double 1
=> 2
irb(main):007:0> double [1,2,3]
=> [2, 4, 6]

(Note, #map is the same as #collect.)


--- spoiler warning ---


An alternative is to use the splat operator and always work with Arrays:

def double(*a)
a.map {|i| i * 2}
end

irb(main):011:0> double 1
=> [2]
irb(main):012:0> double 1,2,3
=> [2, 4, 6]
irb(main):013:0> double [1,2,3]
=> [[1, 2, 3, 1, 2, 3]]

Or, a bit more sophisticated

def double(*a)
a.flatten.map {|i| i * 2}
end

irb(main):017:0> double 1
=> [2]
irb(main):018:0> double 1,2,3
=> [2, 4, 6]
irb(main):019:0> double [1,2,3]
=> [2, 4, 6]

And finally with special treatment of case 1 element:

def double(*a)
a.flatten!

case a.size
when 0
raise ArgumentError, "need values"
when 1
a.first * 2
else
a.map {|i| i * 2}
end
end

irb(main):054:0> double 1
=> 2
irb(main):055:0> double 1,2,3
=> [2, 4, 6]
irb(main):056:0> double [1,2,3]
=> [2, 4, 6]

Kind regards

robert
 
M

Michael Randall

OK, I *thought* that part was working, but now I see that it wasn't
really. So to help me understand I wrote the following, to see what it
does and I see that adding the #inspect will give me the string to
compare against.

But does that mean that (a = [1, 2].class) is setting a to be an Array?
Oh, and thank you!

Here is my test...

irb(main):034:0* def proovy( n )
irb(main):035:1> a = n.class.inspect
irb(main):036:1> b = n.class
irb(main):037:1> puts "a: #{a}"
irb(main):038:1> puts "b: #{b}"
irb(main):039:1> case a
irb(main):040:2> when "Array" : puts "a matched Array in case"
irb(main):041:2> when "Fixnum" : puts "a matched Fixnum in case"
irb(main):042:2> end
irb(main):043:1> case b
irb(main):044:2> when "Array" : puts "b matched Array in case"
irb(main):045:2> when "Fixnum" : puts "b matched Fixnum in case"
irb(main):046:2> end
irb(main):047:1> end
=> nil
irb(main):048:0> proovy(1)
a: Fixnum
b: Fixnum
a matched Fixnum in case
=> nil
irb(main):049:0> proovy([1, 2, 3])
a: Array
b: Array
a matched Array in case
=> nil


Robert said:
2009/10/26 Michael Randall said:
I've attached my test code, "simplea.rb", and below is the output when I
Test double( 3 )

Attachments:
http://www.ruby-forum.com/attachment/4184/simplea.rb

There are a few issues with the first line:

my_a = [ n ].to_a unless n.class == "Array"

First, the test will always fail because the class and the name of the
class are of different types:

irb(main):002:0> Array == "Array"
=> false
[ ... ]
robert
 
M

Michael Linfield

But does that mean that (a = [1, 2].class) is setting a to be an Array?

Consider this:
n = [] => []
a = n.class => Array
a << 1
NoMethodError: undefined method `<<' for Array:Class

a doesn't become an array, it becomes the class type of Array.

Regards,

- Mac
 
M

Michael Randall

Michael said:
But does that mean that (a = [1, 2].class) is setting a to be an Array? Consider this:
n = [] => []
a = n.class => Array
a << 1
NoMethodError: undefined method `<<' for Array:Class

a doesn't become an array, it becomes the class type of Array.
Regards,
- Mac

That makes sense. And I don't know why I didn't think to do the
following to test it myself...

irb(main):061:0> n = []
=> []
irb(main):062:0> a = n.class
=> Array
irb(main):063:0> a.class
=> Class

THANKS!!
 
R

Robert Klemme

Please do not top post.

2009/10/26 Michael Randall said:
OK, I *thought* that part was working, but now I see that it wasn't
really. So to help me understand I wrote the following, to see what it
does and I see that adding the #inspect will give me the string to
compare against.

But does that mean that (a =3D [1, 2].class) is setting a to be an Array?
Oh, and thank you!

Here is my test...

irb(main):034:0* def proovy( n )
irb(main):035:1> =A0 a =3D n.class.inspect
irb(main):036:1> =A0 b =3D n.class
irb(main):037:1> =A0 puts "a: #{a}"
irb(main):038:1> =A0 puts "b: #{b}"
irb(main):039:1> =A0 case a
irb(main):040:2> =A0 =A0 when "Array" =A0: puts "a matched Array in case"
irb(main):041:2> =A0 =A0 when "Fixnum" : puts "a matched Fixnum in case"
irb(main):042:2> =A0 end
irb(main):043:1> =A0 case b
irb(main):044:2> =A0 =A0 when "Array" =A0: puts "b matched Array in case"
irb(main):045:2> =A0 =A0 when "Fixnum" : puts "b matched Fixnum in case"
irb(main):046:2> =A0 end
irb(main):047:1> end

Note that you can simplify the test:

irb(main):004:0> [[], "foo"].each {|obj| case obj
irb(main):005:2> when Array
irb(main):006:2> printf "%p is an array\n", obj
irb(main):007:2> when String
irb(main):008:2> printf "%p is a string\n", obj
irb(main):009:2> else
irb(main):010:2* printf "i don't know what %p is.\n", obj
irb(main):011:2> end}
[] is an array
"foo" is a string
=3D> [[], "foo"]

case uses the =3D=3D=3D operator:

irb(main):012:0> Array =3D=3D=3D [1,2,3]
=3D> true
irb(main):013:0> Array =3D=3D=3D "foo"
=3D> false
irb(main):014:0> String =3D=3D=3D "foo"
=3D> true

IMHO testing against the class is superior to testing against the
type's name. Here's why: in case of inheritance the name test will
fail while the class test still succeeds

irb(main):016:0> class FooArray < Array; end
=3D> nil
irb(main):017:0> Array =3D=3D=3D FooArray.new
=3D> true
irb(main):018:0> "Array" =3D=3D=3D FooArray.name
=3D> false

Kind regards

robert

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

Brian Candler

my_a = [ n ].to_a unless n.class == "Array"

I think what you were trying to write is:

my_a = n
my_a = [ n ] unless n.class == Array

There is actually a Ruby idiom for this:

my_a = Array(n)

Type 'ri Kernel#Array' for more info.

This is a normal method which just happens to start with a capital
letter, which can be confusing. The Ruby interpreter doesn't confuse
this with a constant (or class name) because it is followed by
parentheses, so it must be a method call.

Another version of your original code would be:

my_a = n.is_a?(Array) ? n : [n]

However such class tests are very rare in idiomatic Ruby. Duck-typing
means you don't care what class an object is, just what methods it
responds to.

Since the only method you are going to call is 'collect', you could
write:

my_a = n.respond_to?:)collect) ? n : [n]

Then the caller could pass in *any* object which has a 'collect' method,
and you would use that. Only if it doesn't would it get wrapped in an
Array.

The benefit here is that you could pass in an Enumerable object (such as
an open file), and you could process its elements as they are read
without having to turn the whole file into an Array first.

But equally reasonable would be to drop the [n] part altogether, and
simply define the method as taking an Enumerable object. It would then
be the caller's responsibility to wrap the object if required.

def double( n )
n.collect {|num| num * 2 }
end

puts "\nTest double([1, 2, 3, 4])"
inttest = double( [1, 2, 3, 4] )
puts "\n#{inttest}\n\n"

puts "\nTest double( [3] )"
inttest = double( [3] )
puts "\n#{inttest}\n\n"

This is what tends to happen in idiomatic ruby - the number of lines of
code becomes extremely small :)

HTH,

Brian.
 
M

Michael Randall

Brian Candler wrote a bunch of helpful and educational stuff:
[ ... ]
HTH,

Brian.

More than helpful Brian. Quite educational, and I applaud you.

Michael
 

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

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,143
Latest member
DewittMill
Top