Errore nella funzione shell per contare i numeri pari


12

Per un compito devo scrivere una funzione che stampa il numero di numeri pari quando fornito con una sequenza di numeri.

Ho usato il pezzo di codice che ho usato per un compito precedente (per stampare 1quando un numero era pari e 0quando il numero era dispari)

Il mio problema ora è che la mia funzione continua a stampare 0. Che cosa sto facendo di sbagliato?

Ecco la mia sceneggiatura:

#!/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
}

2
scrivi nel tuo shebang '#! / usr / bin / bash -x' quindi vedrai cosa succede esattamente.
Ziazis,

+1 per dirci che sono i compiti - e per averci lavorato abbastanza duramente per meritare aiuto.
Joe,

Risposte:


20

Hai appena dimenticato di sostituire $#con ( $) elementnel forciclo:

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
}

Ora per testare la funzione:

$ 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

17

@dessert ha riscontrato il problema principale, fornirò una recensione del codice:

  1. Lo shebang: non c'è /usr/bin/bashin Ubuntu. Lo è /bin/bash.
  2. È positivo che tu abbia dichiarato sum localed evitato di inquinare lo spazio dei nomi delle variabili all'esterno della funzione. Inoltre, puoi dichiararlo come una variabile intera usando l' -iopzione:

    local -i sum=0
  3. Cita sempre le tue variabili (e parametri)! Non è necessario in questo script, ma è un'ottima abitudine entrare in:

    for element in "$@"
    do

    Detto questo, puoi omettere il in "$@"qui:

    for element
    do

    Quando in <something>non viene fornito, il forciclo si sovrappone implicitamente agli argomenti. Questo può evitare errori come dimenticare le virgolette.

  4. Non è necessario calcolare e quindi controllare il risultato. Puoi eseguire direttamente il calcolo in if:

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

    (( ... ))è il contesto aritmetico . È più utile che [[ ... ]]per eseguire controlli aritmetici e inoltre è possibile omettere le $variabili precedenti (il che rende più facile la lettura, IMHO).

  5. Se si sposta la parte di controllo uniforme in una funzione separata, è possibile migliorare la leggibilità e la riusabilità:

    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"
    }

1
Se stai cercando un body terser da usare sum=$((sum + 1 - element % 2)).
David Foerster,

1
@DavidFoerster Terser e meno leggibile. ;-)
Konrad Rudolph,

@DavidFoerster: avevo lo stesso pensiero che dovresti semplicemente usare il risultato mod-2 direttamente. (O meglio, &1per controllare il bit basso se è più leggibile per te). Ma possiamo renderlo più leggibile: sum=$((sum + !(element&1) ))usare un inverso booleano invece di +1 - condition. O semplicemente contare gli elementi dispari con ((odd += element&1)), e alla fine stampare con echo $(($# - element)), perché even = total - odd.
Peter Cordes,

Buon lavoro e bello sottolineare piccole cose che mancano ai principianti, ad esempio local -ie sum++.
Paddy Landau,

4

Non sono sicuro che tu sia aperto ad altre soluzioni. Inoltre non so se è possibile utilizzare utility esterne o se si è limitati ai soli built-in bash. Se puoi usare grep, ad esempio, la tua funzione potrebbe essere molto più semplice:

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

In questo modo ogni numero intero di input si trova sulla propria riga e quindi viene utilizzato grepper contare le righe che terminano in una cifra pari.


Aggiornamento - @PeterCordes ha sottolineato che possiamo farlo anche senza grep - solo bash puro, purché l'elenco di input contenga solo numeri interi ben formati (senza punti decimali):

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

Funziona creando un elenco chiamato evensfiltrando tutte le probabilità, quindi restituendo la lunghezza di tale elenco.


Approccio interessante Naturalmente questo conterà 3.0come un numero pari; la domanda non era precisa su quale forma avrebbero preso i numeri.
G-Man dice "Ripristina Monica" il

@G-Man dal momento che bash non supporta l'aritmetica in virgola mobile, è sicuro assumere numeri interi
muru

Poiché la domanda menziona numeri "pari" (e, implicitamente, "dispari"), è in qualche modo sicuro supporre che si tratti di numeri interi. Non vedo come sia valido trarre conclusioni sulla domanda dalle capacità e dai limiti dello strumento che l'utente dovrebbe utilizzare. Ricorda: la domanda dice che si tratta di un compito - immagino per la scuola, perché è troppo banale per essere da un lavoro. Gli insegnanti delle scuole escogitano alcune cose folli.
G-Man dice "Ripristina Monica" il

2
Bash può filtrare un elenco di propria se possiamo supporre nessun elemento contenere spazi bianchi: espandere l'elenco arg con numeri dispari sostituiti con la stringa vuota utilizzando ${/%/}sulla @matrice per richiedere una partita alla fine della stringa, all'interno di un inizializzatore matrice. Stampa il conteggio. Come una battuta per definire ed eseguirlo: foo(){ evens=( ${@/%*[13579]/} ); echo "${#evens[@]} even numbers"; printf "%s\n" "${evens[@]}"; }; foo 135 212 325 3 6 3 4 5 9 7 2 12310. Include effettivamente la stampa dell'elenco per il debug. Stampe 5 even numbers 212 6 4 2 12310(su righe separate)
Peter Cordes,

1
Probabilmente ZSH ha qualcosa per filtrare correttamente un elenco, invece di dipendere dalla suddivisione delle parole per ricostruire un nuovo array (ero un po 'sorpreso che bash non lo facesse; applicando solo elementi di espansione delle stringhe scalari a ogni elemento). Per elenchi di grandi dimensioni, non sarei sorpreso se grepfosse effettivamente più veloce di bash. Hmm, mi chiedo se ci sia una domanda su codegolf dove potrei pubblicare quella funzione bash: P
Peter Cordes
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.