How to interator over two arrays?

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-forum.com/.

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

···

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

"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

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.

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]

···

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]

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

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

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

···

On Thu, 2006-03-30 at 00:18 +0900, baumanj@gmail.com wrote:

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

> 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

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

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.

···

from :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):

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

:slight_smile:

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]

···

On Thu, 2006-03-30 at 03:13 +0900, baumanj@gmail.com wrote:

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

Very nice - thanks to all. Yea I had orginally needed the idea to
calculate the dot product but was also curious if I could return an
array [i.e. the resulting array before being summed].

···

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