(Newbie) Override * on Array

M

Mike Ho

Hi,

I've written a simplistic override of the * operator for Arrays so that
[2,4,6]*[2,2,2] = [4,8,12]

class Array

def *(other)

each_with_index do |x,i|
self= x * other
end
end

end

It assumes that the arrays are the same length and no error checking is
done.

My questions are; is this idiomatic Ruby?
What solution would an experienced Rubyist offer?
How would it be best to handle exceptions when the arrays are of
different dimensions and/or length?


Many Thanks

Mike
 
S

Stefano Crocco

Alle gioved=C3=AC 4 ottobre 2007, Mike Ho ha scritto:
Hi,

I've written a simplistic override of the * operator for Arrays so that
[2,4,6]*[2,2,2] =3D [4,8,12]

class Array

def *(other)

each_with_index do |x,i|
self=3D x * other
end
end

end

It assumes that the arrays are the same length and no error checking is
done.

My questions are; is this idiomatic Ruby?
What solution would an experienced Rubyist offer?
How would it be best to handle exceptions when the arrays are of
different dimensions and/or length?


Many Thanks

Mike


Usually, operators don't modify the operands, but return a new object with =
the=20
result. For instance:

irb: 001> a1 =3D [1, 2 ,3]
[1, 2, 3]
irb: 002> a2 =3D [4,5]
[4, 5]
irb: 003> a1 + a2
[1, 2, 3, 4, 5]
irb: 004> a1
[1, 2, 3]
irb: 005> a2
[4, 5]

Your method, instead, modifies the first operand:

a1 =3D [1,2,3]
a2=3D [4,5,6]
a3 =3D a1*a2
p a1
=3D> [4, 10, 18]
p a3
=3D> [4, 10, 18]

I'd do it this way:

class Array

def *(other)
res =3D []
each_with_index do |x,i|
res=3D x * other
end
res
end

end

or=20

require 'generator'

class Array

def *(other)
SyncEnumerator.new(self, other).map{|x, y| x*y}
end

end

Regarding errors, you I think you can check the size of the two array at th=
e=20
beginning of the method and raise TypeError if they're different.

I hope this helps

Stefano
 
R

Robert Klemme

2007/10/4 said:
Hi,

I've written a simplistic override of the * operator for Arrays so that
[2,4,6]*[2,2,2] = [4,8,12]

class Array

def *(other)

each_with_index do |x,i|
self= x * other
end
end

end

It assumes that the arrays are the same length and no error checking is
done.

My questions are; is this idiomatic Ruby?


No. (see Stefano's reply)
What solution would an experienced Rubyist offer?

irb(main):002:0> require 'enumerator'
=> true
irb(main):003:0> a1=[1,2,3]
=> [1, 2, 3]
irb(main):004:0> a2=[4,5,6]
=> [4, 5, 6]
irb(main):005:0> a3=a1.to_enum:)zip, a2).map {|x,y| x*y}
=> [4, 10, 18]
How would it be best to handle exceptions when the arrays are of
different dimensions and/or length?

If the second array is shorter, you get automatic checking:

irb(main):008:0> (a1+[111]).to_enum:)zip, a2).map {|x,y| x*y}
TypeError: nil can't be coerced into Fixnum
from (irb):10:in `*'
from (irb):10
from (irb):10:in `map'
from (irb):10:in `each'
from (irb):10:in `zip'
from (irb):10:in `each'
from (irb):10:in `map'
from (irb):10
from :0

In the other case the result array just has the length of the first array:

irb(main):009:0> a1.to_enum:)zip, a2+[111]).map {|x,y| x*y}
=> [4, 10, 18]

Depends on what you want to do what kind of error checking you need.

Kind regards

robert
 
S

Stefano Crocco

Alle gioved=C3=AC 4 ottobre 2007, Stefano Crocco ha scritto:
Regarding errors, you I think you can check the size of the two array at
the beginning of the method and raise TypeError if they're different.

Sorry, it should be ArgumentError, not TypeError. Looking at ruby standard=
=20
library, TypeError is used when an object of the wrong type is passed (for=
=20
example, passing an Array to Kernel.Integer), while ArgumentError is used=20
when the argument is of the correct type, but doesn't meet some other=20
requirements (for example, passing a string which doesn't represent a numbe=
r=20
to Kernel.Integer). In this case, the argument is of the correct type=20
(Array), but the requirement "having the same size of the receiver" is not=
=20
met, so I think you should use ArgumentError.

Stefano
 
M

Mike Ho

Stefano said:
require 'generator'

class Array

def *(other)
SyncEnumerator.new(self, other).map{|x, y| x*y}
end

end

Regarding errors, you I think you can check the size of the two array at
the
beginning of the method and raise TypeError if they're different.

I hope this helps

Stefano

Thanks for the replies guys.

The SyncEnumerator looks elegant but when I tried it I got the following
error

in `*': undefined method `*' for nil:NilClass (NoMethodError)

for the line

SyncEnumerator.new(self, other).map{|x, y| x*y}


I call the method by...

a1=[2, 4, 6]
a2= [2, 2, 2]

puts a1*a2

I put some debug in like this

def *(other)
SyncEnumerator.new(self, other).map do |x,y|
puts "x #{x} y #{y}"
x*y
end
end

and the output I get is this
x 2 y 2
x 4 y 2
x 6 y 2
x y
C:/ruby_work/test/scratch.rb:23:in `*': undefined method `*' for
nil:NilClass (NoMethodError)
So it seems to 'run off the end'

Any thoughts ?

Thanks
 
J

John Joyce

Stefano said:
require 'generator'

class Array

def *(other)
SyncEnumerator.new(self, other).map{|x, y| x*y}
end

end

Regarding errors, you I think you can check the size of the two
array at
the
beginning of the method and raise TypeError if they're different.

I hope this helps

Stefano

Thanks for the replies guys.

The SyncEnumerator looks elegant but when I tried it I got the
following
error

in `*': undefined method `*' for nil:NilClass (NoMethodError)

for the line

SyncEnumerator.new(self, other).map{|x, y| x*y}


I call the method by...

a1=[2, 4, 6]
a2= [2, 2, 2]

puts a1*a2

I put some debug in like this

def *(other)
SyncEnumerator.new(self, other).map do |x,y|
puts "x #{x} y #{y}"
x*y
end
end

and the output I get is this
x 2 y 2
x 4 y 2
x 6 y 2
x y
C:/ruby_work/test/scratch.rb:23:in `*': undefined method `*' for
nil:NilClass (NoMethodError)
So it seems to 'run off the end'

Any thoughts ?

Thanks
some things probably shouldn't be overridden.
it's not C++
but try getting similar functionality with a function or method
approach...
 
R

Robert Dober

Alle gioved=EC 4 ottobre 2007, Mike Ho ha scritto:
Hi,

I've written a simplistic override of the * operator for Arrays so that
[2,4,6]*[2,2,2] =3D [4,8,12]

class Array

def *(other)

each_with_index do |x,i|
self=3D x * other
end
end

end

It assumes that the arrays are the same length and no error checking is
done.

My questions are; is this idiomatic Ruby?
What solution would an experienced Rubyist offer?
How would it be best to handle exceptions when the arrays are of
different dimensions and/or length?


Many Thanks

Mike


Usually, operators don't modify the operands, but return a new object wit= h the
result.


#<< being an exception, are there others?

But the important thing is that #* does not and you would need some
very good reason to break this behavior.
I am however much more pragmatic about the second part , personally I
would expect
Array#* an_array to deliver the carthesian product, that all said in
your case I'd just throw an exception if the arrays' sizes do not
match.

Cheers
Robert
--=20
what do I think about Ruby?
http://ruby-smalltalk.blogspot.com/
 
S

Stefano Crocco

Alle gioved=C3=AC 4 ottobre 2007, Mike Ho ha scritto:
Stefano said:
require 'generator'

class Array

def *(other)
SyncEnumerator.new(self, other).map{|x, y| x*y}
end

end

Regarding errors, you I think you can check the size of the two array at
the
beginning of the method and raise TypeError if they're different.

I hope this helps

Stefano

Thanks for the replies guys.

The SyncEnumerator looks elegant but when I tried it I got the following
error

in `*': undefined method `*' for nil:NilClass (NoMethodError)

for the line

SyncEnumerator.new(self, other).map{|x, y| x*y}


I call the method by...

a1=3D[2, 4, 6]
a2=3D [2, 2, 2]

puts a1*a2

I put some debug in like this

def *(other)
SyncEnumerator.new(self, other).map do |x,y|
puts "x #{x} y #{y}"
x*y
end
end

and the output I get is this
x 2 y 2
x 4 y 2
x 6 y 2
x y
C:/ruby_work/test/scratch.rb:23:in `*': undefined method `*' for
nil:NilClass (NoMethodError)
So it seems to 'run off the end'

Any thoughts ?

Thanks


This is strange. It works correctly for me:

require 'generator'

class Array

def *(other)
SyncEnumerator.new(self, other).map{|x, y| x*y}
end

end

a1 =3D [2,4,6]
a2 =3D [2,2,2]
puts a1*a2
=3D> 4
8
12

I can't understand why it isn't working for you.

Stefano
 
M

Mike Ho

Stefano said:
I can't understand why it isn't working for you.

Stefano

Ooops! It does work, I was also extending Fixnum and hacking ops in
there!

Again the SyncEnumerator is elegant especially combined with the map{}

Thanks

Mike
 
B

Brian Adkins

require 'generator'

class Array

def *(other)
SyncEnumerator.new(self, other).map{|x, y| x*y}
end

end

Thanks for the tip on SyncEnumerator.

If a class/module must be opened, I think Enumerator would be better
than Array. Also, I prefer method names over operators in this case,
but that's a personal preference:

require 'generator'
require 'pp'

module Enumerable
def cartesian_product other
self.inject([]) {|result, a| other.each {|b| result << [a, b] };
result }
end

def dot_product other
SyncEnumerator.new(self, other).inject(0) {|s,v| s += v[0]*v[1];
s }
end

def mmap *others, &f
SyncEnumerator.new(self, *others).map(&f)
end

def cross_product other
# exercise for reader
end
end

x = [1, 2, 3]
y = [1, 10, 100]

pp x.dot_product(y)
puts '-'*10
pp x.mmap(y) {|a,b| a*b }
pp x.mmap(y, [1, 2, 4]) {|a,b,c| a*b*c }
puts '-'*10
pp x.cartesian_product(y)
puts '-'*10

BTW, with the mmap call above, I originally got bit by a scope issue
because I named the local variables |x,y|, so the assignment to the
block variables overwrote x & y in the outer scope :(

Personally, I'd prefer to not open a class/module and simply define
functions.

def cartesian_product enum1, enum2
enum1.inject([]) {|result, a| enum2.each {|b| result << [a, b] };
result }
end

def dot_product enum1, enum2
SyncEnumerator.new(enum1, enum2).inject(0) {|s,v| s += v[0]*v[1];
s }
end

def mmap *enums, &f
SyncEnumerator.new(*enums).map(&f)
end

pp dot_product(x, y)
puts '-'*10
pp mmap(x, y) {|a,b| a*b }
pp mmap(x, y, [1, 2, 4]) {|a,b,c| a*b*c }
puts '-'*10
pp cartesian_product(x, y)
 

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
474,268
Messages
2,571,344
Members
48,019
Latest member
Migration_Expert

Latest Threads

Top