web-dev-qa-db-ja.com

Java 8でスタックレス再帰を実現する

Javaでスタックレス再帰を実現するにはどうすればよいですか?

一番出てくると思われる言葉は「トランポリン」で、それが何を意味するのかわかりません。

誰か詳細 Javaでスタックレス再帰を実現する方法を説明できますか?また、「トランポリン」とは何ですか?

それらのいずれかを提供できない場合は、正しい方向に私を向けてください(つまり、それについて読むための本、またはこれらの概念のすべてを教えるチュートリアル)?

22
Matthewacon

トランポリンは、スタックベースの再帰を同等のループに変えるためのパターンです。ループはスタックフレームを追加しないため、これはスタックレス再帰の形式と考えることができます。

これが私が役に立ったと思った図です:

Trampoline diagram

From bartdesmet.net

トランポリンは開始値を取るプロセスと考えることができます。その値を繰り返します。その後、最終値で終了します。


このスタックベースの再帰を考えてみましょう。

public static int factorial(final int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

これが行う再帰呼び出しごとに、新しいフレームがプッシュされます。これは、前のフレームは新しいフレームの結果なしでは評価できないためです。これは、スタックが深くなりすぎてメモリが不足した場合に問題になります。

幸い、この関数はループとして表現できます。

public static int factorial2(int n) {
    int i = 1; 
    while (n > 1) {
        i = i * n;
        n--;
    }
    return i;
}

何が起きてる?再帰的なステップを踏んで、ループ内での反復にしました。すべての再帰ステップが完了するまでループし、結果または各反復を変数に格納します。

作成されるフレームが少なくなるため、これはより効率的です。再帰呼び出しごとにフレーム(nフレーム)を格納する代わりに、現在の値と残りの反復回数(2つの値)を格納します。

このパターンの一般化はトランポリンです。

public class Trampoline<T>
{
    public T getValue() {
        throw new RuntimeException("Not implemented");
    }

    public Optional<Trampoline<T>> nextTrampoline() {
        return Optional.empty();
    }

    public final T compute() {
        Trampoline<T> trampoline = this;

        while (trampoline.nextTrampoline().isPresent()) {
            trampoline = trampoline.nextTrampoline().get();
        }

        return trampoline.getValue();
    }
}

Trampolineには2つのメンバーが必要です。

  • 現在のステップの値。
  • 計算する次の関数、または最後のステップに到達した場合は何もありません

このように記述できる計算はすべて「トランポリン」することができます。

これは階乗ではどのように見えますか?

public final class Factorial
{
    public static Trampoline<Integer> createTrampoline(final int n, final int sum)
    {
        if (n == 1) {
            return new Trampoline<Integer>() {
                public Integer getValue() { return sum; }
            };
        }

        return new Trampoline<Integer>() {
            public Optional<Trampoline<Integer>> nextTrampoline() {
                return Optional.of(createTrampoline(n - 1, sum * n));
            }
        };
    }
}

そして呼び出すために:

Factorial.createTrampoline(4, 1).compute()

メモ

  • ボクシング これはJavaでは非効率になります。
  • このコードはSOで書かれました。テストもコンパイルもされていません

さらに読む

30
sdgfsdh