スタックオーバーフローさせたくないというのであれば、下記のように書くことで回避は可能です。
(下記コードはRuby 2.3.1で確認しています。バージョンが古いとうまくいかないかもしれません)
Ruby
1# frozen_string_literal: true
2RubyVM::InstructionSequence.compile_option = {
3 tailcall_optimization: true,
4 trace_instruction: false
5}
6
7RubyVM::InstructionSequence.compile(<<EOS).eval
8
9def divisors_all(m)
10 divisors_all_r(m, 1, Array.new(0), 0)
11end
12
13def divisors_all_r(m, k, divisors, count)
14 if k > m
15 divisors
16 elsif m % k == 0
17 divisors[count] = k
18 divisors_all_r(m, k + 1, divisors, count + 1)
19 else
20 divisors_all_r(m, k + 1, divisors, count)
21 end
22end
23
24p divisors_all(10000)
25
26EOS
やっていることは末尾呼び出し最適化という手法です。詳細は下記の記事を参考にしてください。
Ruby で末尾呼び出し最適化する方法 - おもしぇてっく
末尾呼び出し最適化は卑怯だ…でもやり方は変えたくないと言うのであれば、単純に次の約数を見つけるとすればいいかと思います。次の呼び出しは約数が見つかったときだけでもいいのですから。
Ruby
1# frozen_string_literal: true
2def divisors_all(m)
3 divisors_all_r(m, 1, [])
4end
5
6def divisors_all_r(m, k, divisors)
7 d = (k..m).find { |i| m % i == 0 }
8 return divisors unless d
9 divisors_all_r(m, d + 1, divisors + [d])
10end
11
12p divisors_all(10000)
ただ、今回の「全ての約数を求める」ということだけが条件であれば、素因数分解して、全ての組合せをかけた物をリストにした方がいいかもしません。
Ruby
1# frozen_string_literal: true
2require 'prime'
3
4def divisors_all(m)
5 [1].product(*m.prime_division.map { |n, e| (0..e).map { |i| n**i } })
6 .map { |list| list.inject(:*) }
7 .sort
8end
9
10p divisors_all(10000)
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。