Forum: Ruby How to interator over two arrays?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Brez! !. (Guest)
on 2006-03-29 08:28
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.
Brez! !. (Guest)
on 2006-03-29 08:29
Title should've been 'How to iterate over two arrays?'..
Dave B. (Guest)
on 2006-03-29 09:04
(Received via mailing list)
"brez! !!" asked:
>   @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 (Guest)
on 2006-03-29 09:44
(Received via mailing list)
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 B. (Guest)
on 2006-03-29 14:36
(Received via mailing list)
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]
unknown (Guest)
on 2006-03-29 19:22
(Received via mailing list)
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 B. (Guest)
on 2006-03-29 19:34
(Received via mailing list)
On Thu, 2006-03-30 at 00:18 +0900, removed_email_address@domain.invalid 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 B. 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...
unknown (Guest)
on 2006-03-29 22:15
(Received via mailing list)
Ross B. 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 B. 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 B. (Guest)
on 2006-03-30 02:43
(Received via mailing list)
On Thu, 2006-03-30 at 03:13 +0900, removed_email_address@domain.invalid wrote:
> Ross B. 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 B. 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]
Brez! !. (Guest)
on 2006-03-31 21:41
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].
This topic is locked and can not be replied to.