Swift : incompréhension des opérateurs && et || dans une boucle while

Bonjour.

Nouveau ici, comme en dev, j’ai commencé l’apprentissage de SWIFT avec Playgrounds. Or, j’ai un souci avec la boucle while et les opérateurs && et ||. Dans un exercice, je dois avancer tant que je n’ai pas activer 4 interrupteurs ET ramassé 3 gemmes. J’avais donc écrit ceci :
while compteurInterrupteurs < 4 && compteurGemmes < 3 {

Mais ce code ne fonctionne pas. Le personnage s’arrête dès qu’il a remplis une des 2 conditions.

Par contre, ce code fonctionne :
while compteurInterrupteurs < 4 || compteurGemmes < 3 {

:|

C’est un bug ou je me trompe ?

Mots clés:

Réponses

  • Le code fonctionne comme il doit fonctionner.
    En fait la boucle doit s'arrêter si : compteurInterrupteurs >= 4 && compteurGemmes >=3
    Elle continue quand ces conditions ne sont remplies, donc
    while not(compteurInterrupteurs >= 4 && compteurGemmes >=3)
    Après plutôt qu'écrire not( condition1 ET condition2), il peut être plus simple de la transformer comme ton second code.

  • Joanna CarterJoanna Carter Membre, Modérateur
    février 2020 modifié #3

    Pour ce type de problème, il faut toujours construire une table de vérité binaire :

    compteurInterrupteurs | compteursGemmes |   AND   |   NOT
    ----------------------------------------------------------
               0          |        0        |    0    |    1
               0          |        1        |    0    |    1
               1          |        0        |    0    |    1
               1          |        1        |    1    |    0
    
    compteurInterrupteurs | compteursGemmes |   OR   |   NOT
    ---------------------------------------------------------
               0          |        0        |    0   |    1
               0          |        1        |    1   |    0
               1          |        0        |    1   |    0
               1          |        1        |    1   |    0
    

    Là, tu peux voir s'il faut l'un ou l'autre.

  • DrakenDraken Membre
    février 2020 modifié #4

    La boucle while permet de répéter une séquence de code, TANT QUE une condition est valable.

    while CONDITION_DE_REPETITION {
        code_a_traiter()
    }
    

    ``
    Alors que ton algorithme défini une condition d'ARRET des tests !

    La solution traditionnelle est d'utiliser l'opérateur booléen ! pour inverser la condition de sortie.

    while !conditionDeSortie {
      code_a_traiter
    }
    

    Dans un exercice, je dois avancer tant que je n’ai pas activer 4 interrupteurs ET ramassé 3 gemmes. J’avais donc écrit ceci :
    while compteurInterrupteurs < 4 && compteurGemmes < 3 {

    Moi j'aurais tapé quelque chose comme :

    while !(compteurInterrupteurs == 4 && compteurGemmes == 3) {
    }
    

    Le programme commence par vérifier la condition de sortie, conformément à ton cahier de charges.

    compteurInterrupteurs == 4 && compteurGemmes == 3

    puis l'opérateur ! inverse la condition de sortie pour obtenir l'information de répétition dont la boucle While a besoin.

    // INVERSION DE LA CONDITION DE SORTIE
    while !CONDITION_DE_SORTIE_EST_VRAIE {
        code_a_traiter
    }
    

    J'ai mis des parenthèses après l'inversion, pour que Swift ne fasse pas d'erreur et inverse bien la totalité de la condition de sortie.

    Sans les parenthèses, l'opérateur ! n'inverse que le résultat du test compteurInterrupteurs==4 et cela devient une belle source de bugs.

    // UN JOLI BUG, PAS FACILE A TROUVER POUR UN NOVICE
    while !compteurInterrupteurs == 4 && compteurGemmes == 3 {
    }
    
  • @Eric P. a dit :
    Après plutôt qu'écrire not( condition1 ET condition2), il peut être plus simple de la transformer comme ton second code.

    S'péce d'optimisateur fou ! Arrête d'embêter le novice avec des optimisations, donc des cas particuliers. Ne l'embrouille pas, pour qu'il puisse se concentrer sur la compréhension des mécanismes de base ..

  • Merci @Eric P.. Mais cela restait obscure...

    En écrivant mon raisonnement pour vous répondre, j’ai compris pourquoi ! Donc maintenant c’est bon.

    PS : c’est vrai qu’en prenant votre logique du not( condition1 ET condition2), j’avais moins de risque de me tromper...

  • @Derw a dit :
    PS : c’est vrai qu’en prenant votre logique du not( condition1 ET condition2), j’avais moins de risque de me tromper...

    Le code doit toujours être simple, et se rapprocher le plus possible de la formulation humaine du problème. C'est la principale raison pour laquelle Swift a été conçu.

  • RenaudRenaud Membre
    février 2020 modifié #8

    @Draken a dit :
    compteurInterrupteurs == 4 && compteurGemmes == 3

    Je me dois de réagir: ce genre de formulation est aussi source de problèmes si on n'a pas de garanties sur la manière dont la progression se fait. Avec par exemple, une séquence de 6 interrupteurs puis 4 gemmes, la boucle tournera de manière infinie. Le code d'Éric est donc plus sur.

  • Merci à tous pour vos contributions ! Et merci @Joanna Carter pour ton tableau !

  • @Renaud a dit :

    Je me dois de réagir: ce genre de formulation est aussi source de problèmes si on n'a pas de garanties sur la manière dont la progression se fait. Avec par exemple, une séquence de 6 interrupteurs puis 4 gemmes, la boucle tournera de manière infinie. Le code d'Éric est donc plus sur.

    C'est la stricte interprétation de la demande de l'exercice de programmation que Derw dt accomplir.

    De plus, ce problème de dépassement des valeurs et de boucle infinie ne peut se produire que si l'application ne vérifie pas les données entrantes.

    S'il faut 4 interrupteurs, l'application ne doit pas permettre d'en utiliser un 5iéme. Il faut un mécanisme de sécurité de ce genre là :

        // Constantes définissant la mission à accomplir
        let nbInterrupteursDemandes = 4
        let nbGemmesDemandees       = 3
    
        // Etat de la mission
        var compteurInterrupteurs = 0
        var compteurGemmes        = 0
    
        // ON TOUCHE UN INTERRUPTEUR
        func toucherInterrupteur () {
            if compteurInterrupteurs == nbInterrupteursDemandes {
                print ("Vous avez déja activé tous les interrupteurs demandés !")
                // Emission d'un bruit négatif
            } else {
                compteurInterrupteurs += 1
            }
        }
    
        // ON TOUCHE UNE GEMME
        func toucherGemme() {
            if compteurGemmes == nbGemmesDemandees {
                print ("Vous avez déja récupéré toutes les gemmes demandées !")
                // Emission d'un bruit négatif
            } else {
                compteurGemmes += 1
            }
        }
    
        // ENCAPSULATION DU TEST DE FIN DE MISSION
        // DANS UNE FONCTION POUR SE SIMPLIFIER LA VIE
        func testerFinMission() -> Bool {
            if compteurInterrupteurs == nbInterrupteursDemandes
                && compteurGemmes == nbGemmesDemandees {
                    return true
            }
            return false
        }
    

    Drew, tu peux remarquer qu'il n'y pas de valeur numérique dans le code. Les paramètres sont définis dans des constantes au début du code. Les chiffres dans le code des fonctions c'est LE MAL ! Surtout si elles sont présentes à plusieurs endroits dans le code. Une erreur de saisie est vite arrivée, ce qui peut causer des bugs.

    L'utilisation de constantes améliore la sécurité et la lisibilité du code. Sans parler de l'évolution éventuelle de l'application. Si le nombre de gemmes à trouver, passe à 4, il suffit de modifier une constante, au lieu de passer 10 minutes à chercher dans le code, toutes les lignes de code concernées (avec toujours le risque d'en oublier une, créant un nouveau bug).

    // Evolution du comportement de l'application
    // en changeant juste une constante, au début du code source
    let nbGemmesDemandees       = 4
    
  • Draken, je trouve ta solution très propre !

  • @Draken a dit :
    C'est la stricte interprétation de la demande de l'exercice de programmation que Derw dt accomplir.

    De plus, ce problème de dépassement des valeurs et de boucle infinie ne peut se produire que si l'application ne vérifie pas les données entrantes.

    Tu as tout à fait raison, mais les deux approches ne sont pas exclusives ;) Si on reste dans l'approche "simple" de l'exercice, utiliser des opérateurs >= plutôt que == permet d'avoir un code plus sur car dépendant de moins d'hypothèses (et donc plus facilement réutilisable). Puis pour le cas particulier de "l'exercice", on peut toujours vérifier les données d'entrées.

    Je faisais aussi ma remarque dans le contexte d'un programme (par opposition à un exercice playground), où le code se trouve dans un environnement susceptible d'évoluer et certaines hypothèses invalidées. Si par exemple des super-gemmes sont ajoutées (qui comptent pour 5 normales, débloquées par achat in-app): un code basé sur des égalités devra être modifié, un code basé sur des minima ne devra pas l'être.

Connectez-vous ou Inscrivez-vous pour répondre.