質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

再帰

情報工学における再帰とは、プログラムのあるメソッドの処理上で自身のメソッドが再び呼び出されている処理の事をいいます。

Q&A

3回答

2125閲覧

Ruby再帰の計算回数を減らしたい

akasatana

総合スコア8

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

再帰

情報工学における再帰とは、プログラムのあるメソッドの処理上で自身のメソッドが再び呼び出されている処理の事をいいます。

0グッド

0クリップ

投稿2016/11/24 16:22

編集2016/11/24 16:27

###実現したいこと
Rubyで正の整数nの全ての約数を配列で出力するプログラムを作りたいのですが、下記を実行するとm=5000付近でスタックオーバーフローになり、エラーになります。

<条件>
・ 少なくともm=10000まで、エラーにならず正常に実行してほしい
再帰法の形を維持し、popやshiftといった配列クラスのメソッドを使わない
・ 整数論なども用いず、平易な計算で完結する

以上の条件のもと、計算回数を節約できるアルゴリズムがあればご教示くださいm(_ _)m

###該当のプログラム

def divisors_all(m) divisors_all_r(m, 1, Array.new(0), 0) end def divisors_all_r(m, k, divisors, count) if k > m divisors elsif m % k == 0 divisors[count] = k divisors_all_r(m, k + 1, divisors, count + 1) else divisors_all_r(m, k + 1, divisors, count) end end

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

スタックオーバーフローさせたくないというのであれば、下記のように書くことで回避は可能です。
(下記コードは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)

投稿2016/11/24 22:09

編集2016/11/24 22:31
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

これ…計算回数という問題ではないですね。kがmの約数であろうがなかろうが再帰が起きるため、必ずm回再帰が発生する事になります。
「kがmの約数なら、m/kもmの約数である」のは自明なので、それを使えば√m回になりはします。
そもそも再帰の使い所がおかしい気もしますが。

投稿2016/11/24 16:28

編集2016/11/24 18:07
swordone

総合スコア20651

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

少しだけ補足をします。

再帰呼び出しでは、メモリのスタック領域上に引数などが確保されることになります。スタック領域はヒープ領域に比べてサイズが小さいため、このような大きな再帰呼出しは実行できません。

また、再帰呼出しは状態記憶用の変数を用いることによってループで書くことができます。

投稿2016/11/24 16:54

MasashiKimura

総合スコア1150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問