web-dev-qa-db-fra.com

Comment réduire les déclarations si

Le programme ci-dessous fonctionne selon les besoins, mais comment puis-je réduire le nombre d'instructions if? On m'a dit que si votre fonction contient au moins deux déclarations si vous ne le faites pas correctement. Aucune suggestion? J'ai essayé d'utiliser des instructions switch mais cela n'a pas fonctionné car le cas ne peut pas être booléen.

for(int i = 1; i < 100; i++)
        {
        if(i % 10 == 3) 
        {
            System.out.println("Fizz" + "(" + i + ") 3%10");
        }

        if(i / 10 == 3)
        {
            System.out.println("Fizz" + "(" + i + ") 3/10");
        }


        if(i % 10 == 5) 
        {
            System.out.println("Buzz" + "(" + i + ") 5%10");
        }

        if(i / 10 == 5)
        {
            System.out.println("Fizz" + "(" + i + ") 5/10");
        }

        if(i / 10 == 7)
        {
            System.out.println("Fizz" + "(" + i + ") 7/10");
        }

        if(i%10 == 7)
        {
            System.out.println("Woof" + "(" + i + ") 7%10");
        }

        if(i % 3 == 0)
        {
            System.out.println("Fizz" + "(" + i + ") 3%==0");
        }

        if(i % 5 == 0)
        {
            System.out.println("Buzz" + "(" + i + ")5%==0");
        }

        if(i % 7 == 0)
        {
            System.out.println("Woof" + "(" + i + ")7%==0");    
        }

        if( (i % 7 !=0 ) && (i % 3 !=0 ) && (i % 5 !=0 )
                && (i % 10 !=3) && (i % 10 !=5 ) && (i%10 !=7 ) )
            System.out.println(i);
    }
24
Calgar99

Pourquoi ne pas créer une méthode pour les cas:

 public void printIfMod(int value, int mod){
       if (value % 10 == mod)
          System.out.println(...);
 }

 public void printIfDiv(int value, int div){
       if (value / 10 == div)
          System.out.println(...);
 }

Ensuite, au lieu d’un groupe de if, vous disposez d’un ensemble d’appels des deux méthodes. Vous pouvez même créer une seule méthode qui appelle les deux éléments ci-dessus.

 public void printIf(int value, int div){
      printIfMod(value, div);
      printIfDiv(value, div);
 }

 for(int i = 1; i < 100; i++) {
      printIf(i, 3);
      printIf(i, 5);
      ....
 }

Dans le code ci-dessus, le nombre de ifs me pose moins de problèmes que le nombre de codes répétés.

49
John B

Voici une légère amélioration en utilisant deux instructions switch

switch(i / 10){
  case 3: // do something
    break;
  case 5: // do something else
    break;
  case 7: // do something else
    break;
}

switch(i % 10){
  case 3: // do something
    break;
  case 5: // do something else
    break;
  case 7: // do something else
    break;
}

Malheureusement, vous aurez besoin d'une instruction switch par diviseur.

Alternativement, vous pouvez embrasser OOP et trouver une abstraction comme celle-ci:

public abstract class Processor {
    private final int divisor;
    private final int result;
    private final boolean useDiv; // if true, use /, else use %

    public Processor(int divisor, int result, boolean useDiv) {
        this.divisor = divisor;
        this.result = result;
        this.useDiv = useDiv;
    }
    public final void process(int i){
        if (
             (useDiv && i / divisor == result)
             || (!useDiv && i % divisor == result)
           ){
                doProcess(i);
            }
    }

    protected abstract void doProcess(int i);
}

Exemple d'utilisation:

public static void main(String[] args) {
    List<Processor> processors = new ArrayList<>();
    processors.add(new Processor(10, 3, false) {
        @Override
        protected void doProcess(int i) {
            System.out.println("Fizz" + "(" + i + ") 3%10");
        }
    });
    // add more processors here
    for(int i = 1; i < 100; i++){
        for (Processor processor : processors) {
            processor.process(i);
        }
    }

}
26

De manière générale, il est vrai que le code comportant de nombreuses déclarations if semble suspect. Méfiant ne veut pas nécessairement dire faux. Si l’énoncé du problème comporte des conditions disjointes à vérifier (c’est-à-dire que vous ne pouvez pas les grouper), vous devez les faire de manière indépendante, comme vous le faites.

Dans votre cas, vous devez vérifier la divisibilité sans pouvoir en déduire l’un de l’autre (c’est-à-dire que x est divisible par 7, cela ne signifie pas qu’il est également divisible par 5, etc.). Tous les numéros que vous utilisez ont été choisis à dessein, c'est la raison pour laquelle vous abordez cette question.

Si, par exemple, ils avaient dit, vérifiez la divisibilité par 2, 3 et 6. Ensuite, vous pouvez d'abord vérifier la valeur de 6, car vous pourriez également sous-entendre la divisibilité par 2 et 3. Ou vice-versa, vérifiez par 2 et 3 et impliquez que il est également divisible par 6. Si tous les nombres sont premiers, vous ne pouvez pas en déduire. Donc, votre code doit vérifier tout individuellement.

Un effet secondaire positif est que cela rend votre intention facile à lire dans votre code (parce que tout est explicite).

Mes deux cents sur ce ...

12
mprivat

Les enums sont un bon ajustement ici. Ils vous permettent d'encapsuler la fonctionnalité dans un emplacement plutôt que de l'étendre à l'ensemble de votre contrôle de flux.

public class Test {
  public enum FizzBuzz {
    Fizz {
      @Override
      String doIt(int n) {
        return (n % 10) == 3 ? "3%10"
                : (n / 10) == 3 ? "3/10"
                : (n / 10) == 5 ? "5/10"
                : (n / 10) == 7 ? "7/10"
                : (n % 3) == 0 ? "3%==0"
                : null;
      }

    },
    Buzz {
      @Override
      String doIt(int n) {
        return (n % 10) == 5 ? "5%10"
                : (n % 5) == 0 ? "5%==0"
                : (n / 10) == 3 ? "3/10"
                : (n / 10) == 5 ? "5/10"
                : (n / 10) == 7 ? "7/10"
                : null;
      }

    },
    Woof {
      @Override
      String doIt(int n) {
        return (n % 10) == 7 ? "7%10"
                : (n % 7) == 0 ? "7%==0"
                : null;
      }

    };

    // Returns a String if this one is appropriate for this n.
    abstract String doIt(int n);

  }

  public void test() {
    // Duplicates the posters output.
    for (int i = 1; i < 100; i++) {
      boolean doneIt = false;
      for (FizzBuzz fb : FizzBuzz.values()) {
        String s = fb.doIt(i);
        if (s != null) {
          System.out.println(fb + "(" + i + ") " + s);
          doneIt = true;
        }
      }
      if (!doneIt) {
        System.out.println(i);
      }
    }
    // Implements the game.
    for (int i = 1; i < 100; i++) {
      boolean doneIt = false;
      for (FizzBuzz fb : FizzBuzz.values()) {
        String s = fb.doIt(i);
        if (s != null) {
          if ( doneIt ) {
            System.out.print("-");
          }
          System.out.print(fb);
          doneIt = true;
        }
      }
      if (!doneIt) {
        System.out.print(i);
      }
      System.out.println();
    }
  }

  public static void main(String args[]) {
    try {
      new Test().test();
    } catch (Throwable t) {
      t.printStackTrace(System.err);
    }
  }

}
8
OldCurmudgeon

Je dirais que vous posez la mauvaise question. La question que je pense que vous devriez poser est la suivante: "Comment puis-je réécrire ce code afin qu'il soit plus facilement compris par un humain?" 

Le credo "éliminer si déclarations" est une idée générale pour y parvenir, mais cela dépend fortement du contexte.

La triste réalité est que beaucoup de réponses masquent cet algorithme très simple sous prétexte de "simplifier les choses". N'introduisez jamais d'objet pour éliminer quelques déclarations if. Dans mon travail, la plupart du code est mis à jour par des personnes moins au fait de l'architecture, des mathématiques et du code que l'auteur d'origine. Il est donc important d'introduire des constructions et une complexité supplémentaires pour réduire le code de 50 à 30 lignes physiques. fois plus difficile à comprendre n'est pas une victoire.

7
John

J'avais commencé à écrire une réponse impliquant du code, mais beaucoup, beaucoup de gens m'ont battue. La seule chose que je dirais qui n’a pas encore été mentionnée est que cette métrique de code particulière à laquelle vous faites référence est appelée complexité cyclomatique et n’est pas une horriblement mauvaise chose. 

En bref, cela fait référence au nombre de chemins différents qu'une méthode peut emprunter lorsqu'elle est exécutée. Bien que le code que vous avez posté ait été assez élevé, il existe de nombreux bons conseils/solutions pour la réduire qui ont été suggérés, personnellement. Je dirais que même dans sa forme actuelle, le code est très lisible - ce qui est un bonus. Cela peut être réduit assez et rester lisible, mais ce que je veux dire, c'est que les métriques de ce type ne sont pas tout, et il est parfois plus simple d'avoir beaucoup d'instructions if car elles sont plus lisibles - et la lisibilité réduit le risque d'erreur , et rend le débogage beaucoup plus facile

Oh, et je remplacerais cette dernière section:

if( (i % 7 !=0 ) && (i % 3 !=0 ) && (i % 5 !=0 )
            && (i % 10 !=3) && (i % 10 !=5 ) && (i%10 !=7 ) )
        System.out.println(i);

En utilisant un indicateur booléen tel que replaced = true chaque fois que l'une des instructions de remplacement est appelée, l'instruction ci-dessus se réduit à:

if (!replaced)
      System.out.println(i);
7
Matt Taylor

Votre code est répétitif. Refactoriser à l'aide de boucles pour votre:

for (int i = 1; i < 100; i++) {
    boolean found = false; // used to avoid the lengthy test for "nothing found"
    for (int j = 3; j <= 7; j += 2) { // loop 3, 5, 7
        if (i % 10 == j) {
            System.out.println("Fizz" + "(" + i + ") "+j+"%10");
            found = true;
        }

        if (i / 10 == j) {
            System.out.println("Fizz" + "(" + i + ") "+j+"/10");
            found = true;
        }

        if (i % j == 0) {
           System.out.println("Fizz" + "(" + i + ") "+j+"%==0");
           found = true;
        }
    }

    if (!found) {
        System.out.println(i);
    }
}
5
Bohemian

Vous pouvez créer plusieurs commutateurs:

switch (i/10) {
     case 3:
        System.out.println("Fizz" + "(" + i + ") 3/10");
        break;

    case 5:
        System.out.println("Fizz" + "(" + i + ") 5/10");
        break;

    case 7:
        System.out.println("Fizz" + "(" + i + ") 7/10");
        break;
    default:
        break;
}

switch (i%10) {
    case 3: 
        System.out.println("Fizz" + "(" + i + ") 3%10");
        break;
    case 5:
        System.out.println("Buzz" + "(" + i + ") 5%10");
        break;
    case 7:
        System.out.println("Woof" + "(" + i + ") 7%10");
        break;
    default:
        break;
}

L'autre cas doit encore utiliser l'instruction if.
Oracle a ajouté une instruction switch qui utilisait String en Java 7. L'instruction boolean switch viendra peut-être plus tard.

1
DeadlyJesus
public class Test
{

    public static void main(String[] args)
    {

        final int THREE = 3;
        final int FIVE = 5;
        final int SEVEN=7;
        final int ZERO = 0;

        for (int i = 1; i < 100; i++)
        {
            modOperation("Fizz", i, THREE);

            divideOperation("Fizz", i, THREE);


            modOperation("Fizz", i, FIVE);

            divideOperation("Buzz", i, FIVE);



            modOperation("Woof", i, SEVEN);

            divideOperation("Fizz", i, SEVEN);


            modOperation("Fizz", i, ZERO);

            divideOperation("Fizz", i, ZERO);
        }

    }

    private static void divideOperation(String sound, int i, int j)
    {
        if (i / 10 == j) // you can add/expand one more parameter for 10 and later on 3 in this example.
        {
            System.out.println(sound + "(" + i + ") "+j+"/10");
        }
    }

    private static void modOperation(String sound, int i, int j)
    {
        if (i % 10 == j)
        {
            System.out.println(sound + "(" + i + ") "+j+"%10");
        }
    }
}

Alors maintenant, vous avez moins if

0
AmitG