Erreur dans la fonction shell pour compter les nombres pairs

12

Pour une affectation, je dois écrire une fonction qui imprime le nombre de nombres pairs lorsqu'il est fourni avec une séquence de nombres.

J'ai utilisé le morceau de code que j'ai utilisé pour une affectation précédente (pour imprimer 1quand un nombre était pair et 0quand le nombre était impair)

Mon problème est maintenant que ma fonction continue d'imprimer 0. Qu'est-ce que je fais mal?

Voici mon script:

#!/usr/bin/bash
# File: nevens.sh

# Write a function called nevens which prints the number of even numbers when provided with a sequence of numbers.
# Check: input nevens 42 6 7 9 33 = output 2

function nevens {

        local sum=0

        for element in $@
        do
                let evencheck=$(( $# % 2 ))
                if [[ $evencheck -eq 0 ]]
                then
                        let sum=$sum+1
                fi
        done

        echo $sum
}
Jedidja
la source
2
écrivez dans votre shebang '#! / usr / bin / bash -x' alors vous verrez ce qui se passe exactement.
Ziazis
+1 pour nous avoir dit que c'était des devoirs - et pour avoir travaillé assez dur pour mériter de l'aide.
Joe

Réponses:

20

Vous avez juste oublié de remplacer $#par ( $) elementdans la forboucle:

function nevens {
  local sum=0
  for element in $@; do
    let evencheck=$(( element % 2 ))
    if [[ $evencheck -eq 0 ]]; then
      let sum=sum+1
    fi
  done
  echo $sum
}

Maintenant, pour tester la fonction:

$ nevens 42 6 7 9 33
2
$ nevens 42 6 7 9 33 22
3
$ nevens {1..10..2} # 1 to 10 step 2 → odd numbers only
0
$ nevens {2..10..2} # 2 to 10 step 2 → five even numbers
5
dessert
la source
17

@dessert a trouvé le problème principal, je vais donner une révision du code:

  1. Le shebang: Il n'y /usr/bin/bashen a pas dans Ubuntu. Ça l'est /bin/bash.
  2. C'est bien que vous ayez déclaré sum localet évité de polluer l'espace de noms de variable en dehors de la fonction. De plus, vous pouvez le déclarer une variable entière en utilisant l' -ioption:

    local -i sum=0
  3. Citez toujours vos variables (et paramètres)! Ce n'est pas nécessaire dans ce script, mais c'est une très bonne habitude à prendre:

    for element in "$@"
    do

    Cela dit, vous pouvez omettre in "$@"ici:

    for element
    do

    Quand in <something>n'est pas donné, la forboucle boucle implicitement sur les arguments. Cela peut éviter des erreurs comme oublier les devis.

  4. Il n'est pas nécessaire de calculer puis de vérifier le résultat. Vous pouvez directement faire le calcul dans if:

    if (( (element % 2) == 0 ))
    then
        ((sum = sum + 1))
    fi

    (( ... ))est le contexte arithmétique . C'est plus utile que [[ ... ]]pour effectuer des vérifications arithmétiques, et en outre, vous pouvez omettre les $variables avant (ce qui le rend plus facile à lire, à mon humble avis).

  5. Si vous avez déplacé la partie de vérification uniforme dans une fonction distincte, cela pourrait améliorer la lisibilité et la réutilisabilité:

    function evencheck
    {
        return $(( $1 % 2 ))
    }
    function nevens
    {
        local -i sum=0
        for element
        do
            # `if` implicitly checks that the returned value/exit status is 0
            if evencheck "$element"
            then
                (( sum++ ))
            fi
        done
        echo "$sum"
    }
muru
la source
1
Si vous cherchez une utilisation de corps de boucle terser sum=$((sum + 1 - element % 2)).
David Foerster
1
@DavidFoerster Terser et moins lisible. ;-)
Konrad Rudolph
@DavidFoerster: J'avais la même pensée que vous devriez simplement utiliser le résultat mod-2 directement. (Ou mieux, &1pour vérifier le bit faible si cela vous semble plus lisible). Mais nous pouvons le rendre plus lisible: sum=$((sum + !(element&1) ))utiliser un inverse booléen au lieu de +1 - condition. Ou tout simplement compter les éléments impairs avec ((odd += element&1)), et à la fin imprimer avec echo $(($# - element)), parce que even = total - odd.
Peter Cordes
Bon travail, et bon de signaler les petites choses qui manquent aux débutants, par exemple local -iet sum++.
Paddy Landau,
4

Je ne sais pas si vous êtes ouvert à d'autres solutions. De plus, je ne sais pas si vous pouvez utiliser des utilitaires externes, ou si vous êtes uniquement limité aux builds bash. Si vous pouvez utiliser grep, par exemple, votre fonction pourrait être beaucoup plus simple:

function nevens {
    printf "%s\n" "$@" | grep -c '[02468]$'
}

Cela place chaque entier d'entrée sur sa propre ligne, puis utilise greppour compter les lignes qui se terminent par un chiffre pair.


Mise à jour - @PeterCordes a souligné que nous pouvons même le faire sans grep - juste du bash pur, tant que la liste d'entrée ne contient que des entiers bien formés (sans virgule décimale):

function nevens{
    evens=( ${@/%*[13579]/} )
    echo "${#evens[@]}"
}

Cela fonctionne en créant une liste appelée evensen filtrant toutes les chances, puis en renvoyant la longueur de cette liste.

Traumatisme numérique
la source
Approche intéressante. Bien sûr, cela comptera 3.0comme un nombre pair; la question n'était pas précise sur la forme que prendraient les chiffres.
G-Man dit `` Réintègre Monica '' le
@ G-Man puisque bash ne prend pas en charge l'arithmétique à virgule flottante, il est prudent de supposer des nombres entiers
muru
Puisque la question mentionne des nombres «pairs» (et, implicitement, «impairs»), il est assez sûr de supposer qu'il s'agit d'entiers. Je ne vois pas comment il est valable de tirer des conclusions sur la question à partir des capacités et des limites de l'outil que l'utilisateur est censé utiliser. Rappelez-vous: la question dit que c'est un devoir - je suppose pour l'école, parce que c'est trop trivial pour être d'un emploi. Les professeurs d'école inventent des choses folles.
G-Man dit `` Réintègre Monica '' le
2
Bash peut filtrer une liste à lui seul si nous pouvons supposer qu'aucun élément ne contient d'espaces: développez la liste arg avec des nombres impairs remplacés par la chaîne vide à l'aide ${/%/}du @tableau pour exiger une correspondance à la fin de la chaîne, à l'intérieur d'un initialiseur de tableau. Imprimez le décompte. En tant que one-liner pour définir et exécuter: foo(){ evens=( ${@/%*[13579]/} ); echo "${#evens[@]} even numbers"; printf "%s\n" "${evens[@]}"; }; foo 135 212 325 3 6 3 4 5 9 7 2 12310. Inclut l'impression de la liste pour le débogage. Impressions 5 even numbers 212 6 4 2 12310(sur lignes séparées)
Peter Cordes
1
Probablement ZSH a quelque chose pour filtrer correctement une liste, au lieu de dépendre du fractionnement de mots pour reconstruire un nouveau tableau (j'étais un peu surpris que bash ne l'ait pas fait; appliquer uniquement des éléments d'extension de chaîne scalaire à chaque élément). Pour les grandes listes, je ne serais pas surpris si grepc'était en fait plus rapide que bash. Hmm, je me demande s'il y a une question de codegolf où je pourrais poster cette fonction bash: P
Peter Cordes