再帰呼び出しの様子を標準出力に表示
再帰呼び出しの様子を可視化するために、以下のように出力する動作を後付けしてみる。
# 普通に再帰関数を定義
def gcd(a, b)
b.zero? ? a.abs : gcd(b, a % b)
end
# 出力するよう指定
require './print_recursive_call'
extend PrintRecursiveCall
print_recursive_call :gcd
# 再帰関数を実行
gcd(1920, -1080)
gcd(1920, -1080)
| gcd(-1080, -240)
| | gcd(-240, -120)
| | | gcd(-120, 0)
| | | #=> 120
| | #=> 120
| #=> 120
#=> 120
関数の呼び出し順序や再帰の深さ、引数と戻り値がひと目でわかる。さすがに再帰の終了条件や値の加工については元のコードを見ないとわからないものの、コードの流れを追うヒントにはなるだろう。
モジュールのコード
設定方法は Module#attr_accessor
をイメージしている。そこでまずはクラス内で呼び出すためのメソッドを作成し、後からトップレベル(mainオブジェクト)でも使えるように微修正した。
メソッドの中では、再帰関数の別名(エイリアス)=コピーを作成しておき、元の名前でコンソール出力を組み込んだラッパー関数として再定義する。また、再帰呼び出しの深さを管理することで、出力時に必要なインデント量がわかる。
module PrintRecursiveCall
INDENT_STR = "| "
def print_recursive_call(*mtds)
mtds.each do |mtd|
mtd_org = :"#{mtd}_noprint"
if respond_to? :alias_method, true
alias_method mtd_org, mtd
else # object main
instance_eval "alias #{mtd_org} #{mtd}"
end
define_method mtd do |*args|
@rec_depth ||= 0
puts INDENT_STR * @rec_depth + "#{mtd}(#{args.map(&:inspect).join(", ")})"
@rec_depth += 1
result = send(mtd_org, *args)
@rec_depth -= 1
puts INDENT_STR * @rec_depth + "#=> #{result}"
result
end
end
end
end
利用する際は、利用したいオブジェクト(その文脈における self
)にメソッドが定義されるように extend または include を用いてモジュールを組み込む。
require './print_recursive_call'
# 特定のクラスでのみ利用 → そのクラス内でextend
class Foo
extend PrintRecursiveCall
print_recursive_call :mtd1 # 定義済みのメソッド名
end
# 全てのクラスで利用 → Classクラスまたはその親クラスでinclude
Module.include PrintRecursiveCall
class Bar
print_recursive_call :mtd2
end
# トップレベルでのみ利用 → extend
extend PrintRecursiveCall
print_recursive_call :mtd3
# どこでも利用 → Objectクラス相当でinclude
include PrintRecursiveCall
print_recursive_call :mtd4
動作サンプル
『再帰的なアルゴリズムの実例集』に書いたものをいくつか可視化してみる。
短い再帰関数は本記事に直接書いているが、元記事のファイルを require './xxx'
などと読み込んでも構わない。(最後の例はそうしている)
[分割統治法] クイックソート
どの再帰呼び出しの中でも同じ関数を2回呼び出す様子が見える。
def quick_sort(ary)
return ary if ary.size <= 1
pivot = ary.pop # 毎回同じ結果になるよう、rand不使用
ary_less_eq, ary_greater = ary.partition { |x| x <= pivot }
quick_sort(ary_less_eq) + [pivot] + quick_sort(ary_greater)
end
require './print_recursive_call'
extend PrintRecursiveCall
print_recursive_call :quick_sort
ary = [5, 1, 3, 9, 7, 0, 4, 2, 8, 6]
quick_sort(ary)
quick_sort([5, 1, 3, 9, 7, 0, 4, 2, 8, 6])
| quick_sort([5, 1, 3, 0, 4, 2])
| | quick_sort([1, 0])
| | | quick_sort([])
| | | #=> []
| | | quick_sort([1])
| | | #=> [1]
| | #=> [0, 1]
| | quick_sort([5, 3, 4])
| | | quick_sort([3])
| | | #=> [3]
| | | quick_sort([5])
| | | #=> [5]
| | #=> [3, 4, 5]
| #=> [0, 1, 2, 3, 4, 5]
| quick_sort([9, 7, 8])
| | quick_sort([7])
| | #=> [7]
| | quick_sort([9])
| | #=> [9]
| #=> [7, 8, 9]
#=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[メモ化再帰] フィボナッチ数列
処理が進むにつれて再帰呼び出しせず値を返すようになっていく様子が見える。
$table = [0, 1]
def fibonacci(n)
$table[n] ||= fibonacci(n-2) + fibonacci(n-1)
end
require './print_recursive_call'
extend PrintRecursiveCall
print_recursive_call :fibonacci
fibonacci(5)
puts
fibonacci(5) # メモ済みの状態でもう1回
fibonacci(5)
| fibonacci(3)
| | fibonacci(1)
| | #=> 1
| | fibonacci(2)
| | | fibonacci(0)
| | | #=> 0
| | | fibonacci(1)
| | | #=> 1
| | #=> 1
| #=> 2
| fibonacci(4)
| | fibonacci(2)
| | #=> 1
| | fibonacci(3)
| | #=> 2
| #=> 3
#=> 5
fibonacci(5)
#=> 5
ちなみに fibonacci(n-2)
と fibonacci(n-1)
を入れ替えると再帰呼び出しの形が変わる。
[相互再帰] 再帰下降構文解析
再帰関数が複数になっても問題ない。コードは長いため転記せず require で読み込む。
require './expr_parser'
require './print_recursive_call'
# 定義済みのクラスを開いて処理を追加
class ExprParser
extend PrintRecursiveCall
print_recursive_call :additive, :multitive, :primary, :decimal
end
ExprParser.new.parse("1+2*(3*(4+5)+6)*(7+8)+9")
additive(0)
| multitive(0)
| | primary(0)
| | | decimal(0)
| | | #=> [1, 1]
| | #=> [1, 1]
| #=> [1, 1]
| multitive(2)
| | primary(2)
| | | decimal(2)
| | | #=> [3, 2]
| | #=> [3, 2]
| | primary(4)
| | | decimal(4)
| | | #=> [nil, nil]
| | | additive(5)
| | | | multitive(5)
| | | | | primary(5)
| | | | | | decimal(5)
| | | | | | #=> [6, 3]
| | | | | #=> [6, 3]
| | | | | primary(7)
| | | | | | decimal(7)
| | | | | | #=> [nil, nil]
| | | | | | additive(8)
| | | | | | | multitive(8)
| | | | | | | | primary(8)
| | | | | | | | | decimal(8)
| | | | | | | | | #=> [9, 4]
| | | | | | | | #=> [9, 4]
| | | | | | | #=> [9, 4]
| | | | | | | multitive(10)
| | | | | | | | primary(10)
| | | | | | | | | decimal(10)
| | | | | | | | | #=> [11, 5]
| | | | | | | | #=> [11, 5]
| | | | | | | #=> [11, 5]
| | | | | | #=> [11, 9]
| | | | | #=> [12, 9]
| | | | #=> [12, 27]
| | | | multitive(13)
| | | | | primary(13)
| | | | | | decimal(13)
| | | | | | #=> [14, 6]
| | | | | #=> [14, 6]
| | | | #=> [14, 6]
| | | #=> [14, 33]
| | #=> [15, 33]
| | primary(16)
| | | decimal(16)
| | | #=> [nil, nil]
| | | additive(17)
| | | | multitive(17)
| | | | | primary(17)
| | | | | | decimal(17)
| | | | | | #=> [18, 7]
| | | | | #=> [18, 7]
| | | | #=> [18, 7]
| | | | multitive(19)
| | | | | primary(19)
| | | | | | decimal(19)
| | | | | | #=> [20, 8]
| | | | | #=> [20, 8]
| | | | #=> [20, 8]
| | | #=> [20, 15]
| | #=> [21, 15]
| #=> [21, 990]
| multitive(22)
| | primary(22)
| | | decimal(22)
| | | #=> [23, 9]
| | #=> [23, 9]
| #=> [23, 9]
#=> [23, 1000]
引数の数字は文字列の開始位置、戻り値の数字の組は文字列の終了位置と部分的な数式の計算結果である。この動作のうち構文解析成功部(nilを返していないところ)だけを見やすく図にしたものが以下の絵になる。
Author And Source
この問題について(再帰呼び出しの様子を標準出力に表示), 我々は、より多くの情報をここで見つけました https://qiita.com/HMMNRST/items/63942c68cac3a7dd83c1著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .