Błąd w funkcji powłoki, aby liczyć liczby parzyste

12

Aby wykonać zadanie, muszę napisać funkcję, która wypisze liczbę parzystą, jeśli otrzyma ciąg liczb.

Użyłem fragmentu kodu, którego użyłem do poprzedniego zadania (aby wydrukować, 1gdy liczba była parzysta, a 0kiedy liczba była nieparzysta)

Mój problem polega na tym, że moja funkcja nadal drukuje 0. Co ja robię źle?

Oto mój skrypt:

#!/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
źródło
2
napisz w swoim shebang „#! / usr / bin / bash -x”, a zobaczysz, co dokładnie się stanie.
Ziazis
+1 za powiedzenie nam, że to praca domowa - i za to, że ciężko nad nią pracowaliśmy, aby zasłużyć na pomoc.
Joe

Odpowiedzi:

20

Właśnie zapomniałeś zamienić na $#( $) elementw forpętli:

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
}

Teraz, aby przetestować funkcję:

$ 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
deser
źródło
17

@dessert znalazł podstawowy problem, dam przegląd kodu:

  1. The shebang: W /usr/bin/bashUbuntu nie ma . Jest /bin/bash.
  2. Dobrze, że zadeklarowałeś sum locali unikałeś zanieczyszczenia przestrzeni nazw zmiennych poza funkcją. Dodatkowo możesz zadeklarować ją jako zmienną całkowitą za pomocą -iopcji:

    local -i sum=0
  3. Zawsze podawaj swoje zmienne (i parametry)! W tym skrypcie nie jest konieczne, ale bardzo dobry nawyk, aby dostać się do:

    for element in "$@"
    do
    

    To powiedziawszy, możesz pominąć in "$@"tutaj:

    for element
    do
    

    Jeśli in <something>nie podano, forpętla domyślnie zapętla się nad argumentami. Pozwala to uniknąć błędów, takich jak zapomnienie cytatów.

  4. Nie ma potrzeby obliczania, a następnie sprawdzania wyniku. Możesz bezpośrednio wykonać obliczenia w if:

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

    (( ... ))jest kontekstem arytmetycznym . Jest to bardziej przydatne niż [[ ... ]]do sprawdzania arytmetycznego, a dodatkowo można pominąć $zmienne przed (co ułatwia odczyt, IMHO).

  5. Jeśli część sprawdzającą parzystość została przeniesiona do oddzielnej funkcji, może to poprawić czytelność i możliwość ponownego użycia:

    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
źródło
1
Jeśli szukasz ciała z pętlą terserową, użyj sum=$((sum + 1 - element % 2)).
David Foerster,
1
@DavidFoerster Terser i mniej czytelny. ;-)
Konrad Rudolph
@DavidFoerster: Tak samo myślałem, że powinieneś po prostu użyć bezpośrednio wyniku mod-2. (Lub lepiej, &1aby sprawdzić niski bit, jeśli jest to dla ciebie bardziej czytelne). Ale możemy sprawić, by było bardziej czytelne: sum=$((sum + !(element&1) ))użyć odwrotnej wartości logicznej zamiast +1 - condition. Lub po prostu policz nieparzyste elementy za pomocą ((odd += element&1)), a na końcu wydrukuj za pomocą echo $(($# - element)), ponieważ even = total - odd.
Peter Cordes,
Dobra praca i dobre podkreślić małe rzeczy, które początkujący przeoczyć, np local -ia sum++.
Paddy Landau
4

Nie jestem pewien, czy jesteś otwarty na inne rozwiązania. Nie wiem też, czy możesz korzystać z zewnętrznych narzędzi, czy też jesteś ograniczony do wbudowanych bashów. Jeśli możesz grepna przykład użyć , twoja funkcja może być o wiele prostsza:

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

To umieszcza każdą liczbę całkowitą na swojej linii, a następnie używa grepdo zliczania linii kończących się parzystą cyfrą.


Aktualizacja - @PeterCordes wskazało, że możemy to zrobić nawet bez grep - tylko czysty bash, o ile lista wejściowa zawiera tylko dobrze uformowane liczby całkowite (bez kropek dziesiętnych):

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

Działa to poprzez utworzenie listy wywoływanej evensprzez odfiltrowanie wszystkich szans, a następnie zwrócenie długości tej listy.

Cyfrowa trauma
źródło
Ciekawe podejście Oczywiście będzie to liczone 3.0jako liczba parzysta; pytanie nie było precyzyjne, jaką formę przyjmą liczby.
G-Man mówi „Przywróć Monikę”
@ G-Man, ponieważ bash nie obsługuje arytmetyki zmiennoprzecinkowej, można bezpiecznie przyjmować liczby całkowite
muru
Ponieważ w pytaniu jest mowa o „parzystych” (i domyślnie „nieparzystych”) liczbach, można bezpiecznie założyć, że chodzi o liczby całkowite. Nie rozumiem, jak uzasadnione jest wyciąganie wniosków dotyczących pytania na podstawie możliwości i ograniczeń narzędzia, którego oczekuje się od użytkownika. Pamiętaj: pytanie mówi, że jest to zadanie - myślę, że do szkoły, ponieważ jest to zbyt trywialne, aby być z pracy. Nauczyciele szkolni wymyślają jakieś szalone rzeczy.
G-Man mówi „Przywróć Monikę”
2
Bash może samodzielnie filtrować listę, jeśli możemy założyć, że żaden element nie zawiera białych znaków: rozwiń listę argumentów liczbami nieparzystymi zastąpionymi pustym łańcuchem, używając ${/%/}w @tablicy wymagającej dopasowania na końcu łańcucha, wewnątrz inicjatora tablicy. Wydrukuj liczbę. Jako jedną wkładką do zdefiniowania i uruchomić go: foo(){ evens=( ${@/%*[13579]/} ); echo "${#evens[@]} even numbers"; printf "%s\n" "${evens[@]}"; }; foo 135 212 325 3 6 3 4 5 9 7 2 12310. Obejmuje faktycznie wydrukowanie listy do debugowania. Odciski 5 even numbers 212 6 4 2 12310(na osobnych liniach)
Peter Cordes,
1
Prawdopodobnie ZSH ma coś do prawidłowego filtrowania listy, zamiast polegać na dzieleniu słów w celu przebudowania nowej tablicy (byłem trochę zaskoczony, że bash tego nie zrobił; zastosowanie tylko skalarnych elementów rozszerzających ciąg do każdego elementu). W przypadku dużych list nie byłbym zaskoczony, gdyby grepfaktycznie był szybszy niż bash. Hmm, zastanawiam się, czy istnieje pytanie o kodegolfa, w którym mógłbym opublikować tę funkcję bash: P
Peter Cordes