[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

How to interator over two arrays?

brez! !!

3/29/2006 4:29:00 AM

Let me start by saying I really like Ruby iterators - anyhow I've been
taking every opportunity to use the single line syntax like:

StopWord.find(:all).each{ |x| @stop_words << x.stopword }

yea!

But I came across one that I couldn't quite figure out.. The basic idea
is called a 'dot product' or 'simple matching' - it's a way to determine
simularities in vector space models - that's not important tho.. I'm
looking for a way to iterate over two arrays and sum or multiply or
whatever each element resulting in a new array of the summed elements,
e.g.

a[0]+b[0], a[1]+b[1], ... a[n]+b[n]


This is what I'm currently using - it works but def lacks the eloquence
of single line iterators:

#assumes equal size arrays!
def dotproduct(doc, query)
@product = Array.new(doc.length)
@i = 0
doc.each do |term|
@product[@i] = term * query[@i]
@i += 1
end
return sum(@product)
end

Any thoughts on getting this into single-line syntax? Just curious.

Thanks.

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


8 Answers

brez! !!

3/29/2006 4:29:00 AM

0

Title should've been 'How to iterate over two arrays?'..

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


Dave Burt

3/29/2006 5:02:00 AM

0

"brez! !!" asked:
> Let me start by saying I really like Ruby iterators - anyhow I've been
> taking every opportunity to use the single line syntax like:
>
> StopWord.find(:all).each{ |x| @stop_words << x.stopword }
>
> yea!
> ...
> #assumes equal size arrays!
> def dotproduct(doc, query)
> @product = Array.new(doc.length)
> @i = 0
> doc.each do |term|
> @product[@i] = term * query[@i]
> @i += 1
> end
> return sum(@product)
> end
>
> Any thoughts on getting this into single-line syntax? Just curious.

Here ya go:

dotproduct = doc.zip(query).inject(0) {|sum, (d, q)| sum + d * q }

Cheers,
Dave


rickhg12hs

3/29/2006 5:39:00 AM

0

Here are a couple hacks:

def dotproduct(a,b)
(0..a.length-1).inject(0) {|s,i| s + a[i]*b[i]}
end

or perhaps:

require 'generator'
def dotproduct(a,b)
SyncEnumerator.new(a,b).inject(0) {|s,(i,j)| s+i*j}
end

There's no type checking here, etc. User beware.
I've also found that using 'generator' can be pretty slow sometimes.

I'll watch for better responses too.

Ross Bamford

3/29/2006 10:36:00 AM

0

On Wed, 2006-03-29 at 13:28 +0900, brez! !! wrote:

> I'm looking for a way to iterate over two arrays and sum or multiply or
> whatever each element resulting in a new array of the summed elements,
> e.g.
>
> a[0]+b[0], a[1]+b[1], ... a[n]+b[n]

Just a footnote to the other solutions you have for this, you can avoid
inject to make it a bit shorter using map (thanks to the way block
argument assignment works with arrays):

ary = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]

bry = ary.dup
# => [1, 2, 3, 4, 5]

ary.zip(bry).map! { |a,b| a+b }
# => [2, 4, 6, 8, 10]

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk



baumanj

3/29/2006 3:19:00 PM

0

I don't see how this is an alternative for inject; it's just a
different need. You use map when you want to generate a new Enumerable
and inject when you want to generate a single value. The answer to the
question "how does one iterate over multiple collections in parallel?"
is just to use zip and the fact that assignment of an array to multiple
variables puts the values of the elements into the variables.

Ross Bamford wrote:
> On Wed, 2006-03-29 at 13:28 +0900, brez! !! wrote:
>
> > I'm looking for a way to iterate over two arrays and sum or multiply or
> > whatever each element resulting in a new array of the summed elements,
> > e.g.
> >
> > a[0]+b[0], a[1]+b[1], ... a[n]+b[n]
>
> Just a footnote to the other solutions you have for this, you can avoid
> inject to make it a bit shorter using map (thanks to the way block
> argument assignment works with arrays):
>
> ary = [1,2,3,4,5]
> # => [1, 2, 3, 4, 5]
>
> bry = ary.dup
> # => [1, 2, 3, 4, 5]
>
> ary.zip(bry).map! { |a,b| a+b }
> # => [2, 4, 6, 8, 10]
>
> --
> Ross Bamford - rosco@roscopeco.REMOVE.co.uk

Ross Bamford

3/29/2006 3:32:00 PM

0

On Thu, 2006-03-30 at 00:18 +0900, baumanj@gmail.com wrote:
> I don't see how this is an alternative for inject; it's just a
> different need. You use map when you want to generate a new Enumerable
> and inject when you want to generate a single value. The answer to the
> question "how does one iterate over multiple collections in parallel?"
> is just to use zip and the fact that assignment of an array to multiple
> variables puts the values of the elements into the variables.
>

Glad you top posted this one, makes it easy to quote the original post:

> Ross Bamford wrote:
> > On Wed, 2006-03-29 at 13:28 +0900, brez! !! wrote:
> >
> > > I'm looking for a way to iterate over two arrays and sum or multiply or
> > > whatever each element resulting in a new array of the summed elements,
> > > e.g.
> > >
> > > a[0]+b[0], a[1]+b[1], ... a[n]+b[n]
> >

Notice OP wanted to sum or multiply each element *resulting in a new
array of summed elements*. Other solutions posted injected an array:

arr.zip(brr).inject([]) { |ary,(a,b)| ary << a + b }

And I was just generally commenting that any time you inject an array
and want a one-for-one transformation you can avoid that and use map. I
obviously didn't mean that #map is a general-purpose alternative to
#inject...

> > Just a footnote to the other solutions you have for this, you can avoid
> > inject to make it a bit shorter using map (thanks to the way block
> > argument assignment works with arrays):
> >
> > ary = [1,2,3,4,5]
> > # => [1, 2, 3, 4, 5]
> >
> > bry = ary.dup
> > # => [1, 2, 3, 4, 5]
> >
> > ary.zip(bry).map! { |a,b| a+b }
> > # => [2, 4, 6, 8, 10]
> >
> > --
> > Ross Bamford - rosco@roscopeco.REMOVE.co.uk

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk



baumanj

3/29/2006 6:10:00 PM

0

Ross Bamford wrote:
> Notice OP wanted to sum or multiply each element *resulting in a new
> array of summed elements*. Other solutions posted injected an array:
>
> arr.zip(brr).inject([]) { |ary,(a,b)| ary << a + b }

Ah yes, but if you look at the original post, you'll see that what he's
actually calculating is the dot product (a single value) and that his
code returns a single value (assuming he defined the sum method):

brez! !! wrote:
> #assumes equal size arrays!
> def dotproduct(doc, query)
> @product = Array.new(doc.length)
> @i = 0
> doc.each do |term|
> @product[@i] = term * query[@i]
> @i += 1
> end
> return sum(@product)
> end

So despite what he said, it appears what he actually wants is inject.
If so, the first response is correct:

Dave Burt wrote:
> dotproduct = doc.zip(query).inject(0) {|sum, (d, q)| sum + d * q }

The zip solution you posted isn't quite right, because it creates an
array of the sums of the elements rathar than the products and doesn't
produce the actual dot product value. I think what you meant was:

arr.zip(brr).inject([]) { |ary,(a,b)| ary << a * b }

But that just generates the array of products, not their sum. If both
the value and the array of products are required, I don't think there's
a one-line solution without getting really ugly. It seems to me the
original poster is confused about ruby variables. The original code
seems to use instance variables where local variables are probably more
appropriate. If this is what he really meant, then it's equivalent with
the original answer. More explicitly:

>> def dotproduct(doc, query)
>> product = Array.new(doc.length)
>> i = 0
>> doc.each do |term|
?> product[i] = term * query[i]
>> i += 1
>> end
>> return sum(product)
>> end
=> nil
>> def dotproduct2(doc, query)
>> doc.zip(query).inject(0) {|sum, (d, q)| sum + d * q }
>> end
=> nil
>> a = [1,2,3]
=> [1, 2, 3]
>> b = [4,5,6]
=> [4, 5, 6]
>> dotproduct(a, b)
NoMethodError: undefined method `sum' for main:Object
from (irb):8:in `dotproduct'
from (irb):15
from :0
>> def sum(e)
>> e.inject {|i,j| i+j}
>> end
=> nil
>> dotproduct(a, b)
=> 32
>> dotproduct2(a, b)
=> 32

> And I was just generally commenting that any time you inject an array
> and want a one-for-one transformation you can avoid that and use map. I
> obviously didn't mean that #map is a general-purpose alternative to
> #inject...

Oh, sorry. I was just confused as to what you meant.

Ross Bamford

3/29/2006 10:42:00 PM

0

On Thu, 2006-03-30 at 03:13 +0900, baumanj@gmail.com wrote:
> Ross Bamford wrote:
> > Notice OP wanted to sum or multiply each element *resulting in a new
> > array of summed elements*. Other solutions posted injected an array:
> >
> > arr.zip(brr).inject([]) { |ary,(a,b)| ary << a + b }
>
> Ah yes, but if you look at the original post, you'll see that what he's
> actually calculating is the dot product (a single value) and that his
> code returns a single value (assuming he defined the sum method):
>

Okay, I didn't look too deeply beyond the first example (of the array
that was wanted, a[0] + b[0], a[1] + b[1], ..., a[n] + a[n]) so I didn't
realise about the exact nature of what was needed. I don't think it
makes a real difference to how you'd code it, though...?

Anyway, assuming the sum method is defined, he could just do:

sum(arr.zip(brr).map! { |a,b| a * b })

:)

> So despite what he said, it appears what he actually wants is inject.
> If so, the first response is correct:
>
> Dave Burt wrote:
> > dotproduct = doc.zip(query).inject(0) {|sum, (d, q)| sum + d * q }
>

As I said, I wasn't suggesting any incorrectness in the posted
solutions, just adding a footnote on a general point. I do now see that
inject was the way to go in this particular case.

> The zip solution you posted isn't quite right, because it creates an
> array of the sums of the elements rathar than the products and doesn't
> produce the actual dot product value. I think what you meant was:
>
> arr.zip(brr).inject([]) { |ary,(a,b)| ary << a * b }
>
> But that just generates the array of products, not their sum. If both
> the value and the array of products are required, I don't think there's
> a one-line solution without getting really ugly.

Yes, I suppose I did mean that. Perhaps I should have quoted directly
rather than paraphrasing. And it probably depends on your definition of
ugly (and of 'one-line' too I guess), but if both were needed you could
do:

ary = [1,2,3,4,5]
bry = [1,2,3,4,5]
s,*p = ary.zip(bry).inject([0]) {|arr,(a,b)| arr[0]+=(p=a*b) and arr<<p}

p s
# => 55

p p
# => [1, 4, 9, 16, 25]

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk