web-dev-qa-db-fra.com

Sortir d'une récursion en java

La récursivité est une sorte de style "diviser pour régner", elle se scinde en diminuant (structure de données arborescente) et je veux qu'elle se brise complètement si une violation est trouvée, ce qui signifie rompre tous les chemins récursifs et renvoyer true. Est-ce possible?

22
Stasis

Vous pouvez retourner un code d'erreur ou modifier une variable globale afin que chaque instance récursive sache "se tuer".

Quelque chose du genre.

int foo(bar){
     int to_the_next;

      if (go_recursive){
            to_the_next = foo(whisky_bar);

            if (to_the_next ==DIE) return DIE;
      }

      if (something_unexpected_happened) return DIE;

      process;//may include some other recursive calls, etc etc
}
15
Tom

Peu importe ce que vous faites, vous allez devoir dérouler la pile. Cela laisse deux options:

  1. Valeur de retour magique (comme décrit par l'un des Tom)
  2. Lancer une exception (comme mentionné par thaggie)

Si le cas où vous voulez que les choses meurent soit rare, il peut s'agir d'une de ces situations où le lancement d'une exception peut être un choix viable. Et avant que tout le monde ne me prenne dans la gorge, rappelez-vous qu'une des règles les plus importantes de la programmation est de savoir quand il convient de déroger à la règle.

Il s’est avéré que j’avais passé aujourd’hui à évaluer la bibliothèque zxing à partir de Google Code. Ils utilisent en réalité des projections d'exception pour de nombreuses structures de contrôle. Ma première impression quand j'ai vu c'était l'horreur. Ils appelaient littéralement des méthodes des dizaines de milliers de fois avec différents paramètres jusqu'à ce que la méthode ne lève pas d'exception.

Cela ressemblait certainement à un problème de performances, alors j’ai fait quelques ajustements pour changer les choses en utilisant une valeur de retour magique. Et tu sais quoi? Le code était 40% plus rapide lors de l'exécution dans un débogueur. Mais lorsque je suis passé au non-débogage, le code était moins de 1% plus rapide.

Je ne suis toujours pas fou de la décision d'utiliser des exceptions pour le contrôle de flux dans ce cas (je veux dire, les exceptions sont levées tout l'heure). Mais cela ne vaut certainement pas la peine que je prenne le temps de le réappliquer étant donné la différence de performance presque incommensurable.

Si votre condition qui déclenche la mort de l'itération n'est pas une partie fondamentale de l'algorithme, l'utilisation d'une exception peut rendre votre code beaucoup plus propre. Pour moi, le point où je prendrais cette décision est que si toute la récursivité doit être résolue, j'utiliserais une exception. SI seule une partie de la récursivité doit être déroulée, utilisez une valeur de retour magique.

36
Kevin Day

S'il s'agit d'un seul fil effectuant la récursion, vous pouvez générer une exception . Un peu moche cependant: utilisez une exception comme goto.

boolean myPublicWrapperMethod(...) {
    try {
        return myPrivateRecursiveFunction(...);
    } catch (MySpecificException e) {
        return true;
    } 
} 

Une meilleure approche consisterait à éliminer la récursivité et à utiliser une collection Stack contenant une classe représentant ce qui aurait été un état récursif, à effectuer une itération dans une boucle et à renvoyer la valeur true quand vous le souhaitez.

5
Tom

Je recommanderais un traitement d'exception. Cela indique clairement que la récursivité a été annulée à cause d'une violation (ou d'une autre exception):

public void outer() {
  try {
    int i = recurse(0);
  } catch (OuchException ouch) {
    // handle the exception here
  }
}

private int recurse(int i) throws OuchException {

    // for tree-like structures
    if (thisIsALeaf) {
       return i;
    }

    // the violation test
    if (itHurts)
       throw new OuchException("That really hurts");

    // we also can encapsulate other exceptions
    try {
       someMethod();
    } catch (Exception oops) {
       throw new OuchException(oops);
    }

    // recurse
    return recurse(i++);
}

Bien sûr, cela viole l'exigence initiale de rendre «vrai» lors d'un avortement. Mais je préfère une séparation nette des valeurs de retour et une notification du comportement anormal.

4
Andreas_D

Vous pouvez faire quelque chose de similaire en stockant une variable qui indique si les récursions doivent être interrompues ou non. Malheureusement, vous devrez vérifier chaque récursion, mais vous pouvez le faire.

1
Joe Phillips

Ce que vous demandez, c'est la définition de la récursivité.

À un moment donné, tous les chemins récursifs devraient casser. Sinon, ce sera une récursion infinie et une exception de débordement de pile se produira.

Donc, vous devriez concevoir la fonction de récursivité comme celle-là. Un exemple recherche binaire dans un tableau trié :

BinarySearch(A[0..N-1], value, low, high) {
       if (high < low)
           return -1 // not found
       mid = low + ((high - low) / 2)  // Note: not (low + high) / 2 !!
       if (A[mid] > value)
           return BinarySearch(A, value, low, mid-1)
       else if (A[mid] < value)
           return BinarySearch(A, value, mid+1, high)
       else
           return mid // found
}
1
Emre

Le meilleur moyen de sortir d'une boucle récursive lorsqu'une erreur est rencontrée est de lancer une exception d'exécution.

throw new RuntimeException("Help!  Somebody debug me!  I'm crashing!");

Bien sûr, cela tue votre programme, mais vous devez utiliser la vérification de la plage et l'analyse algorithmique pour vous assurer que votre récursivité ne lève pas une telle exception. Une des raisons pour lesquelles vous pourriez vouloir sortir d'un algorithme récursif est que vous manquez de mémoire. Ici, il est possible de déterminer la quantité de mémoire que votre algorithme utilisera sur la pile. Si vous codez Java, comparez ce calcul à

getMemoryInfo.availMem().

Supposons que vous utilisez la récursivité pour trouver n !. Votre fonction ressemble à ceci:

public long fact(int n)
{
    long val = 1;
    for (int i  = 1, i<=n,i++)
        return val *= fact(i);
    return val;
}

Avant de l'exécuter, vérifiez que vous avez (nombre d'octets dans un long, 8 en Java) * n octets en mémoire pour contenir toute la pile. Fondamentalement, avec la vérification de la plage et des erreurs avant la méthode/fonction récursive, vous ne devriez pas avoir besoin d’éclater. Toutefois, selon votre algorithme, vous devrez peut-être signaler à toute la pile que vous êtes prêt à partir. Si c'est le cas, la réponse de Tom fonctionne.

0
samdoj

À moins que les appels récursifs ne soient évalués en parallèle, il vous suffira probablement d'ajouter une logique pour vérifier la valeur du premier appel récursif avant de lancer le deuxième appel récursif (puis ultérieur.

public abstract class Tree {

    protected abstract boolean headIsViolation();

    protected abstract boolean isLeaf();

    public Tree getLeft();

    public Tree getRight();

    // Recursive
    public boolean checkViolation() {
        if(this.isLeaf()) {
            return this.headIsViolation();
        }

        // If needed, you could pass some sort of 'calculation state'
        // object through the recursive calls and evaluate the violation
        // through that object if the current node is insufficient
        if(this.headIsViolation()) {
            // Terminate the recursion
            return true;
        }

        // Fortunately, Java short-circuits ||
        // So if the left child is in violation, the right child will
        // not even be checked
        return this.getLeft().checkViolation() || this.getRight().checkViolation();
    }
}
0
Greg Case

Peut-être voudrez-vous éviter la récursivité et le remplacer par une pile. Cela vous donne le contrôle de sortir de l'opération, tout en pouvant faire quelque chose qui ressemble à une opération récursive. En fait, vous imitez la récursivité par vous-même.

J'ai trouvé une bonne explication ici: http://haacked.com/archive/2007/03/04/Replacing_Recursion_With_a_Stack.aspx/

0
murphy

J'ai été capable de sortir de tous les appels récursifs en utilisant une variable globale.

boolean skipRecursivePaths = false;
private callAgain(){

if(callAgain){
  if(getOutCompletely){

    skipRecursivePaths = true;
    }
    if(skipRecursivePaths){
    return;
    }
}
0
roho