Kesalahan dalam fungsi shell untuk menghitung angka genap

12

Untuk tugas saya harus menulis fungsi yang mencetak jumlah angka genap ketika disediakan dengan urutan angka.

Saya menggunakan potongan kode yang saya gunakan untuk tugas sebelumnya (untuk mencetak 1ketika nomor genap dan 0ketika nomor itu ganjil)

Masalah saya sekarang adalah fungsi saya terus mencetak 0. Apa yang saya lakukan salah?

Ini skrip saya:

#!/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
sumber
2
tulis di shebang Anda '#! / usr / bin / bash -x' maka Anda akan melihat apa yang sebenarnya terjadi.
Ziazis
+1 memberi tahu kami bahwa ini adalah pekerjaan rumah - dan karena telah mengerjakannya dengan cukup keras sehingga layak mendapatkan bantuan.
Joe

Jawaban:

20

Anda lupa mengganti $#dengan ( $) elementdi forloop:

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
}

Sekarang untuk menguji fungsinya:

$ 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
pencuci mulut
sumber
17

@dabut telah menemukan masalah inti, saya akan memberikan beberapa tinjauan kode:

  1. Shebang: Tidak ada /usr/bin/bashdi Ubuntu. Ini /bin/bash.
  2. Ada baiknya Anda mendeklarasikan sum local, dan menghindari mencemari namespace variabel di luar fungsi. Selain itu, Anda dapat mendeklarasikannya sebagai variabel integer menggunakan -iopsi:

    local -i sum=0
  3. Selalu kutip variabel Anda (dan parameter)! Tidak perlu dalam skrip ini, tetapi kebiasaan yang sangat baik untuk masuk:

    for element in "$@"
    do

    Yang mengatakan, Anda dapat menghilangkan di in "$@"sini:

    for element
    do

    Ketika in <something>tidak diberikan, forloop secara implisit loop atas argumen. Ini dapat menghindari kesalahan seperti melupakan kutipan.

  4. Tidak perlu menghitung dan kemudian memeriksa hasilnya. Anda dapat langsung melakukan perhitungan di if:

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

    (( ... ))adalah konteks aritmatika . Ini lebih berguna daripada [[ ... ]]untuk melakukan pemeriksaan aritmatika, dan selain itu Anda dapat menghilangkan $variabel sebelumnya (yang membuatnya lebih mudah dibaca, IMHO).

  5. Jika Anda memindahkan bagian pemeriksa ke fungsi yang terpisah, itu mungkin meningkatkan keterbacaan dan penggunaan kembali:

    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
sumber
1
Jika Anda sedang mencari penggunaan tubuh terser loop sum=$((sum + 1 - element % 2)).
David Foerster
1
@DavidFoerster Terser dan kurang mudah dibaca. ;-)
Konrad Rudolph
@ DavidFoerster: Saya memiliki pemikiran yang sama bahwa Anda harus menggunakan hasil mod-2 secara langsung. (Atau lebih baik, &1untuk memeriksa bit rendah jika itu lebih mudah dibaca untuk Anda). Tapi kita bisa membuatnya lebih mudah dibaca: sum=$((sum + !(element&1) ))menggunakan boolean inverse sebagai gantinya +1 - condition. Atau hitung saja unsur-unsur aneh dengan ((odd += element&1)), dan pada akhirnya dicetak dengan echo $(($# - element)), karena even = total - odd.
Peter Cordes
Kerja bagus, dan bagus untuk menunjukkan hal-hal kecil yang dilewatkan oleh pemula, misalnya local -idan sum++.
Paddy Landau
4

Saya tidak yakin apakah Anda terbuka untuk solusi lain. Juga saya tidak tahu apakah Anda dapat menggunakan utilitas eksternal, atau apakah Anda murni terbatas pada bash builtin. Jika Anda dapat menggunakan grep, misalnya, fungsi Anda bisa menjadi jauh lebih sederhana:

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

Ini menempatkan setiap integer input pada barisnya sendiri, dan kemudian digunakan grepuntuk menghitung garis yang berakhir pada angka genap.


Pembaruan - @PeterCordes menunjukkan bahwa kita bahkan dapat melakukan ini tanpa grep - hanya bash murni, selama daftar input berisi bilangan bulat yang terbentuk dengan baik (tanpa titik desimal):

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

Ini berfungsi dengan membuat daftar yang dipanggil evensdengan menyaring semua peluang, lalu mengembalikan panjang daftar itu.

Trauma Digital
sumber
Pendekatan yang menarik. Tentu saja ini akan dihitung 3.0sebagai angka genap; pertanyaannya tidak tepat tentang bagaimana bentuk angka-angka itu
G-Man Mengatakan 'Reinstate Monica'
@ G-Man karena bash tidak mendukung aritmatika titik apung, aman untuk menganggap bilangan bulat
muru
Karena pertanyaan menyebutkan angka "genap" (dan, secara implisit, "ganjil"), agak aman untuk mengasumsikan bahwa itu berbicara tentang bilangan bulat. Saya tidak melihat bagaimana valid untuk menarik kesimpulan tentang pertanyaan dari kemampuan dan keterbatasan alat yang diharapkan pengguna gunakan. Ingat: pertanyaannya mengatakan ini adalah tugas - saya kira untuk sekolah, karena ini terlalu sepele dari pekerjaan. Guru sekolah membuat beberapa hal gila.
G-Man Mengatakan 'Reinstate Monica'
2
Bash dapat memfilter daftar sendiri jika kita dapat mengasumsikan tidak ada elemen yang mengandung spasi putih: perluas daftar arg dengan angka ganjil diganti dengan string kosong menggunakan ${/%/}pada @array untuk memerlukan kecocokan di akhir string, di dalam inisialisasi array. Cetak hitungan. Sebagai satu-kapal untuk mendefinisikan dan menjalankannya: foo(){ evens=( ${@/%*[13579]/} ); echo "${#evens[@]} even numbers"; printf "%s\n" "${evens[@]}"; }; foo 135 212 325 3 6 3 4 5 9 7 2 12310. Termasuk sebenarnya mencetak daftar untuk debugging. Cetakan 5 even numbers 212 6 4 2 12310(pada baris sep.)
Peter Cordes
1
Mungkin ZSH memiliki sesuatu untuk memfilter daftar dengan benar, alih-alih tergantung pada pemisahan kata untuk membangun kembali array baru (saya agak terkejut, tapi tidak; hanya menerapkan ekspansi skalar string hal untuk setiap elemen). Untuk daftar besar, saya tidak akan terkejut jika grepsebenarnya lebih cepat daripada bash. Hmm, saya bertanya-tanya apakah ada pertanyaan codegolf di mana saya bisa memposting fungsi bash itu: P
Peter Cordes