Rubyで逆数の和を算出する方法


 逆数の和を算出する問題を解いたので記録しておきます。

1/1 = 1

1/1 + 1/2 = 1.5

1/1 + 1/2 + 1/3 = 1.8333...

 このような形で1からNまでの整数の逆数の和を出していき、その逆数の和が7を超える最小の値Nを出したいと思います。
 最終的にコードは以下のようになりました。答えは「616」です。

num = []
sum = 0
count = 0
while sum < 7
  count += 1
  reciprocal = 1.0/count
  num << reciprocal
  sum = num.sum
end
puts num.length

 プログラミング初心者ということもあり、めちゃめちゃ非効率なやり方ではありますが、論理が飛躍しないように地道に複数のやり方を試して答えに辿り着きました。
 というわけで以下の流れで答えに辿り着いたプロセスを紹介したいと思います。

  1. 小数を含める
  2. とりあえずN=20で試してみた
  3. 強引に答えを出してみた
  4. もう少し可読性を上げる

1. 小数を含める

 まずは4までの逆数の和を出そうとしてみました。

num = 1/1 + 1/2 + 1/3 + 1/4
puts num

 しかし上記のコードでは実行結果は「1」となってしまいました。どうやら小数が切り捨てられてしまうみたいです。そこで「to_f」を使ってみると、実行結果は「2.08333...」となり、小数点も含まれるようになりました。

num = 1/1.to_f + 1/2.to_f + 1/3.to_f + 1/4.to_f
puts num

 ただこんなことをしなくても「1」を「1.0」に変えるだけでも小数が含まれるようになりました。「整数÷整数=整数」という仕様になっているようなので「.0」をつけるだけで良かったみたいです。

num = 1.0/1 + 1.0/2 + 1.0/3 + 1.0/4
puts num

2. とりあえずN=20で試してみた。

 だいたい答えがどれくらい大きな数字になるかというアタリをつける目的で、とりあえず20回繰り返し処理を試してみました。

num = 0
20.times do |i|
  num += 1.0/(i + 1)
  puts num
end

 そうすると実行結果は以下のようになりました。

1.0
1.5
1.8333333333333333
2.083333333333333
2.283333333333333
2.4499999999999997
2.5928571428571425
2.7178571428571425
2.8289682539682537
2.9289682539682538
3.0198773448773446
3.103210678210678
3.180133755133755
3.251562326562327
3.3182289932289937
3.3807289932289937
3.439552522640758
3.4951080781963135
3.547739657143682
3.597739657143682

 N=20で逆数の和は「3.597...」ですし、足していく逆数はどんどん小さくなるため、逆数の和が7を超える最小の値はかなり大きな数字になりそうです。勘では当てられそうにありません。

3. 強引に答えを出してみた

 もちろんここから原始的なやり方で答えを導くことも可能だと言えます。とりあえず1万回繰り返し処理をしてみて、逆数の和が7を超えた時に繰り返し処理をストップさせてみました。また、何回目でストップしたかが分かるようにブロック変数の中身を「timesCount」にしてみました。

sum = 0
10000.times do |timesCount|
  count = timesCount + 1
  sum += 1.0/count
  if sum >= 7
   puts sum
   puts count
   return
  end
end

 これで実行結果は以下のようになりました。

7.001274097134162
616

 N = 616の時に初めて逆数の和が7になるという答え自体は導き出せました。

4. もう少し可読性を上げる

 答えを出すことだけが目的ならば上記のコードでも問題はないですが、とりあえず1万回繰り返し処理をしてみたというのはコードとしては不適切です。そのため、whileを使ってコードを書き直してみました。

num = []
sum = 0
count = 0
while sum < 7
  count += 1
  reciprocal = 1.0/count
  num << reciprocal
  sum = num.sum
end
puts num.length

 それが最初に紹介したこのコードです。合計が7を超えるまで繰り返し処理を行うようにして、配列に値を追加しながら合計を出すようにしていきました。配列を使っているため、lengthメソッドでNの値を出すようにしています。

 以上です。もっと適切なコードはあるはずですので、ご意見を頂けるとありがたいです。