How to interator over two arrays?

B

brez! !!

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

Dave Burt

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

rickhg12hs

Here are a couple hacks:

def dotproduct(a,b)
(0..a.length-1).inject(0) {|s,i| s + a*b}
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.
 
R

Ross Bamford

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

baumanj

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 said:
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]
 
R

Ross Bamford

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 said:
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]
 
B

baumanj

Ross said:
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! !! said:
#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 said:
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 = term * query
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=> 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.
 
R

Ross Bamford

Ross said:
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:

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]
 

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,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top