(Newbie) Override * on Array

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[i]= x * other[i]
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

Alle giovedì 4 ottobre 2007, Mike Ho ha scritto:

  self[i]= x * other[i]

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
result. For instance:

irb: 001> a1 = [1, 2 ,3]
[1, 2, 3]
irb: 002> a2 = [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 = [1,2,3]
a2= [4,5,6]
a3 = a1*a2
p a1
=> [4, 10, 18]
p a3
=> [4, 10, 18]

I’d do it this way:

class Array

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

end

or

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

2007/10/4, Mike Ho [email protected]:

  self[i]= x * other[i]
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

Stefano C. wrote:

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

Alle giovedì 4 ottobre 2007, Stefano C. 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
library, TypeError is used when an object of the wrong type is passed
(for
example, passing an Array to Kernel.Integer), while ArgumentError is
used
when the argument is of the correct type, but doesn’t meet some other
requirements (for example, passing a string which doesn’t represent a
number
to Kernel.Integer). In this case, the argument is of the correct type
(Array), but the requirement “having the same size of the receiver” is
not
met, so I think you should use ArgumentError.

Stefano

On Oct 4, 2007, at 7:51 AM, Mike Ho wrote:

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

x 6 y 2
Posted via http://www.ruby-forum.com/.

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

On 10/4/07, Stefano C. [email protected] wrote:

each_with_index do |x,i|

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
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

Alle giovedì 4 ottobre 2007, Mike Ho ha scritto:

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

  x*y

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 = [2,4,6]
a2 = [2,2,2]
puts a1*a2
=> 4
8
12

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

Stefano

On Oct 4, 7:04 am, Stefano C. [email protected] wrote:

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| abc }
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 :frowning:

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| abc }
puts ‘-’*10
pp cartesian_product(x, y)

Stefano C. wrote:

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