2

I'm not getting correct results from the following monkey-patching method in Integer:

def harm
  1 + (2..self).inject{|sum, x| sum + 1/x.to_r}
end

2.harm #=> 3

it should return 3/2 instead, where is my mistake?

Ivan Prodanov
  • 34,634
  • 78
  • 176
  • 248

3 Answers3

4

There are two problems here:

  1. When you iterate across a closed range, such as 2..2, nothing actually happens:

    (0..0).inject(){|s, x| s+= 99 }
    # => 0
    

    Which is why you get 3, as 1 + 2 is 3.

  2. If you do not pass an argument into inject, it uses the first value you pass into the iterator as the starting memo, i.e. 2:

    (2..2).inject(){|s, x| s+= 99 }
    #=> 2
    

    Putting in a 0 gets you an actual iteration:

    (2..2).inject(0){|s, x| s+= 99 }
    #=> 99
    

So try this instead in your method:

1 + (2..self).inject(0){|sum, x| sum + 1/x.to_r}  

Standalone:

1 + (2..2).inject(0){|sum, x| sum + 1/x.to_r}  
#=> 3/2
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
dancow
  • 3,228
  • 2
  • 26
  • 28
  • 1
    Your first sentence is correct, but `1 + 2 + 1/2.to_r` is not. Is that is the case, the result would be `7/2`. – sawa Oct 13 '13 at 14:59
  • Edited it...the `inject` behaves differently than I thought when no initial memo is passed in – dancow Oct 13 '13 at 15:47
2

Here is the tip(you need to pass the initial value to the inject method):

def harm
  1 + (2..2).inject(0){|sum, x| sum + 1/x.to_r}
end

harm # => (3/2)

Documentation of Enumerable#inject:

If you specify a block, then for each element in enum the block is passed an accumulator value (memo) and the element. If you specify a symbol instead, then each element in the collection will be passed to the named method of memo. In either case, the result becomes the new value for memo. At the end of the iteration, the final value of memo is the return value for the method.

If you do not explicitly specify an initial value for memo, then the first element of collection is used as the initial value of memo.

Community
  • 1
  • 1
Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317
  • What is the default initial value? – Ivan Prodanov Oct 13 '13 at 14:01
  • 1
    You don't *need* to pass an initial value, the initial value is the first item in the array if you don't explicitly pass one. – user229044 Oct 13 '13 at 14:03
  • @meagar Try the same code in your IRB with the initial argument as `0` and without `0`.. You can see the diff.. and the reason is also explained in the documentation.. I linked and pasted here also.. :) – Arup Rakshit Oct 13 '13 at 14:28
  • @ArupRakshit I'm aware of how `inject` works. The initial value is the first item in the enumerable if you don't explicitly pass one, and this is usually fine. – user229044 Oct 13 '13 at 14:29
1

In the 1 minute of time that I decided to spend over your question, I was unable to realize what's wrong with your code. But I was able to write this method that does something similar to what you want to do:

class Integer
  def harm
    return 0 if self == 0
    return -(-self).harm if self < 0
    ( 1 .. self ).map { |n| Rational 1, n }.reduce :+
  end
end

0.harm #=> 0
2.harm #=> 3/2
7.harm #=> 363/140
-2.harm #=> (-3/2)

Note, though, that for large number, this highly readable code becomes inefficient, as it prepares the array in the memory before performing the summation.

Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74