関数を呼び出すときにパラメーターとして送信せずに、再帰内でインデックス変数を使用したい。ただし、最初にリセットすると (i = 0 など)、実行するたびにリセットされます。カウンターとして使用したい(関数の実行をカウントするため)。
5 に答える
まず第一に、明らかに一度だけ初期化したいでしょう。再帰の一般的なパターンは次のとおりです。
public void run(int opt) {
run_helper(opt, 0);
}
private void run(int opt, int depth) {
if (whatever) { run(opt, depth + 1); }
}
外側のメソッドは、初期化以外は何も行いません。
頻繁に提案される「解決策」(たとえば、質問への最初のコメントなど)は、静的変数を使用することです。このアプローチは悪いスタイルであり、マルチスレッドを追加すると、さまざまな奇妙な方法でプログラムが失敗する原因となります (たとえば、UI バージョンを作成したり、マルチスレッド Web サーバーで実行したりするなど)。最悪の場合、最初は機能しているように見えても、多くのユーザーがいる場合にのみ微妙に誤動作し始めることがあります。したがって、定数以外のすべてについて「静的」を避けてください。
「静的」では、一般的に次のようになります。
static int counter;
public void start() {
counter = 0;
recurse();
}
public void recurse() {
counter += 1;
if (whatever) { recurse(); }
}
ここで、2 人のユーザーがstart
同時に呼び出すとします。彼らはお互いのカウンターを上書きします!静的とは、スレッドとユーザー間で共有されることを意味するためです。
これは非常に理解しやすい解決策です:
class MyTask {
int counter = 0;
public void recurse() {
counter++;
if (whatever) { recurse(); }
}
public int getIterations() {
return counter;
}
}
public void run() {
MyTask task = new MyTask();
task.run();
System.out.println("Task took "+task.getIterations()+" itertions.");
}
次に、タスクを作成して実行し、最後にカウンターを取得します。クリーンで、非常にシンプルで、効率的で信頼性があります。複数のスレッド/ユーザーがある場合、それぞれに個別の MyTask オブジェクトがあり、問題は発生しません。
さらに、追加の統計を追加することができ、それらはすべてタスク オブジェクトにきれいにラップされます。「反復あたりの平均時間」?「平均再帰深度」?問題ない。タスク オブジェクトを使用して、結果を保存することもできます。
の使用はThreadLocal
ここで提案されています。私はこれに同意しません。タスク オブジェクトの利点はまったくありません。ThreadLocal
と task オブジェクトを使って実装してみるだけで、違いがわかります。さらに、ThreadLocal
経験的にヒープ値にアクセスするよりも 10 倍遅くなります ( https://stackoverflow.com/a/4756605/1060350を参照)。それint
はおそらくさらに悪いからです。したがって、パフォーマンス クリティカルな codepath を決して呼び出さないでThreadLocal#get
ください。を使用する場合ThreadLocal
は、このコードパスの外で使用し、ローカル変数 (または Task オブジェクト) を使用して、「ローカル静的」変数を重要なコードパスにフィードします。
2 つの方法を使用して分離する必要があります。1 つは再帰反復を開始してカウンターをゼロに初期化するための public であり、もう 1 つは再帰呼び出しが行われる場所である非公開です。このようにして、パブリック メソッドを呼び出すたびにカウンターが初期化されます。これは次のようになります (Java の場合):
public class Recursion{
private int iterations=0;
private int calcFactorial(int n){
iterations++;
if (n==2)
return 2;
else
return n * calcFactorial(n-1);
}
public int factorial(int n){
//initialize the counter
iterations = 0;
return calcFactorial(n);
}
public int getIterations(){
return iterations;
}
}
属性を使用できますが、なぜですか?値をパラメータとして渡してみませんか?
public class Empty {
private int steps = 0;
public static void main (String args [])
{
Empty e = new Empty ();
System.out.println (e.reverse ("This is a test"));
System.out.println ("steps: " + e.steps);
}
public String reverse (String s) {
++steps;
if (s.length () < 2)
return s;
else return reverse (s.substring (1)) + s.charAt (0);
}
}
私が得たコメントから、再帰をいつ終了するかを検出するためのカウンターとして使用します。それはあまり役に立たないようです。list.length()などのパラメーターから導出できる条件はありませんか?
もちろん、メソッドを2回呼び出すと、カウンターがさらに増えます。2つのスレッドで同時に使用されていない場合はリセットできます。また、内部オブジェクトのラップされたメソッドは、最初にカウンターをリセットせずに呼び出すのを防ぐのに役立つ場合があります。
しかし、それはカウンターを回すよりもはるかに定型的です。
パラメータは、呼び出しをカウントするためのはるかにクリーンなソリューションです。
デバッグ中のスタックオーバーフローを防止したい場合、好奇心をそそる代替手段は、ランダマイザーを呼び出して、10000のケースのうちの1つを返すことです。
インデックスが停止条件として使用されることを考えると、単純なパラメーター ベースのアプローチを使用します。JavaScript の実装:
var recurse = function() {
recurseHelper(0);
};
var recurseHelper = function(iteration) {
// run for 100 iterations,
if (iterationCount > 100)
return;
// do stuff...
recurseHelper(iteration + 1);
}
ただし、関数を特定の回数だけ呼び出したい場合、特に再帰を使用する必要があるのはなぜですか? ループを使用できます。再び JavaScript で:
for (var i = 0; i < 100; i++) {
// do stuff...
}
コンパイラは、再帰アルゴリズムの複雑さによっては、再帰構造よりもループの巻き戻しの方が楽しい場合があります。
最も自然なことは、特に再帰をいつ停止するかを決定するために使用される場合、投稿で説明したように補助パラメーターを使用することです。メンバー変数に依存している場合は、再帰メソッドを呼び出す前にカウンターをリセットするヘルパー メソッドを用意し、再帰関数を呼び出したいときはいつでもヘルパー メソッドを呼び出すことができます。
単一の方法に固執したい場合は、次のようにする必要があります。
insideMethod
デフォルトで false に設定されているというメンバー変数を作成します。メソッドが呼び出されると、この変数が検査されます。false の場合は最初の呼び出しであり、カウンターをリセットする必要があります。それ以外の場合はカウンターをインクリメントする必要があります。その後、insideMethod
は true に設定されます。戻るときに、true に設定されたのがこのinsideMethod
呼び出しである場合にのみ、false に設定し直す必要があります。
insideMethod
インデックス変数を ThreadLocalにすることを忘れないでください。
擬似コード:
ThreadLocal<Boolean> insideMethod = false
ThreadLocal<Integer> index = 0
....
void recMethod(args) {
boolean topCall = (insideMethod == false)
insideMethod = true
if (topCall)
index = 0
else
index++
// Body of the method...
if (topCall)
insideMethod = false
}