[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Newbie) Override * on Array

Mike Ho

10/4/2007 10:53:00 AM

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
--
Posted via http://www.ruby-....

9 Answers

Stefano Crocco

10/4/2007 11:04:00 AM

0

Alle giovedì 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] = [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

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


Robert Klemme

10/4/2007 11:26:00 AM

0

2007/10/4, Mike Ho <mike_houghton@xyratex.com>:
> 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?

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 Crocco

10/4/2007 11:38:00 AM

0

Alle giovedì 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
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

Mike Ho

10/4/2007 12:52:00 PM

0

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

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

John Joyce

10/4/2007 1:09:00 PM

0


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

> Stefano Crocco 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
>
> --
> Posted via http://www.ruby-....
>
some things probably shouldn't be overridden.
it's not C++
but try getting similar functionality with a function or method
approach...

Robert Dober

10/4/2007 1:33:00 PM

0

On 10/4/07, Stefano Crocco <stefano.crocco@alice.it> wrote:
> Alle giovedì 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] = [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
>
> 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
--
what do I think about Ruby?
http://ruby-smalltalk.blo...

Stefano Crocco

10/4/2007 6:33:00 PM

0

Alle giovedì 4 ottobre 2007, Mike Ho ha scritto:
> Stefano Crocco 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


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

Mike Ho

10/5/2007 8:54:00 AM

0

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

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

Brian Adkins

10/5/2007 5:27:00 PM

0

On Oct 4, 7:04 am, Stefano Crocco <stefano.cro...@alice.it> 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| 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)