Çift sayıları saymak için kabuk işlevinde hata


12

Bir ödev için, bir dizi sayı sağlandığında çift sayıların sayısını yazdıran bir işlev yazmam gerekir.

Önceki bir ödev için kullandığım kod parçasını kullandım ( 1bir sayı çift 0olduğunda ve sayı tek olduğunda yazdırmak için)

Benim sorunum şimdi fonksiyonumun yazdırmaya devam etmesi 0. Neyi yanlış yapıyorum?

İşte benim senaryom:

#!/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
``!! / usr / bin / bash -x '' yazıp tam olarak ne olduğunu göreceksiniz.
Ziazis

Bize ödevini söylediğin için - ve yardımı hak edecek kadar çok çalıştığın için.
Joe

Yanıtlar:


20

Sadece yerine unuttum $#(ile $) elementiçinde fordöngü:

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
}

Şimdi işlevi test etmek için:

$ 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

@ tatlı temel sorunu buldu, bazı kod incelemesi vereceğim:

  1. Mesele: /usr/bin/bashUbuntu'da hiç yok . Öyle /bin/bash.
  2. sum localİşlev adının dışında değişken ad alanını kirletmekten kaçınmanız ve kaçınmanız iyi olur . Ayrıca, şu -iseçeneği kullanarak bir tamsayı değişkeni bildirebilirsiniz :

    local -i sum=0
  3. Her zaman değişkenlerinizi (ve parametrelerinizi) belirtin! Bu senaryoda gerekli değil, içine girmek için çok iyi bir alışkanlık:

    for element in "$@"
    do

    Bununla birlikte, in "$@"burada atlayabilirsiniz :

    for element
    do

    Verilmediğinde in <something>, fordöngü bağımsız değişkenlerin üzerinden dolaylı olarak döner. Bu, tırnak işaretlerini unutmak gibi hataları önleyebilir.

  4. Sonucu hesaplamaya ve kontrol etmeye gerek yoktur. Hesaplamayı doğrudan şurada yapabilirsiniz if:

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

    (( ... ))olan aritmetik bağlam . [[ ... ]]Aritmetik kontroller yapmaktan daha kullanışlıdır ve ek olarak $önceki değişkenleri (okumayı kolaylaştırır, IMHO) atlayabilirsiniz .

  5. Eşit denetleme parçasını ayrı bir işleve taşıdıysanız, okunabilirliği ve yeniden kullanılabilirliği artırabilir:

    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
Bir terser döngü vücut kullanımı arıyorsanız sum=$((sum + 1 - element % 2)).
David Foerster

1
@DavidFoerster Terser ve daha az okunabilir. ;-)
Konrad Rudolph

@DavidFoerster: Sadece mod-2 sonucunu doğrudan kullanmanız gerektiği düşüncesindeydim. (Ya da daha iyisi, sizin &1için daha okunabilir olup olmadığını düşük biti kontrol etmek için). Ancak daha okunabilir hale getirebiliriz: sum=$((sum + !(element&1) ))yerine boole tersini kullanmak +1 - condition. Ya da sadece tek elemanları sayın ((odd += element&1))ve sonunda yazdırın echo $(($# - element)), çünkü even = total - odd.
Peter Cordes

İyi iş ve yeni başlayanların kaçırdığı küçük şeylere dikkat çekmek iyi, örn. local -iVe sum++.
Paddy Landau

4

Başka çözümlere açık olup olmadığınızdan emin değilim. Ayrıca harici yardımcı programları kullanıp kullanamayacağınızı veya tamamen bash yerleşikleriyle sınırlı olup olmadığınızı bilmiyorum. grepÖrneğin kullanabiliyorsanız , işleviniz çok daha basit olabilir:

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

Bu, her girdi tamsayısını kendi satırına yerleştirir ve ardından grepçift ​​basamakla biten satırları saymak için kullanır .


Güncelleme - @PeterCordes, giriş listesi sadece iyi biçimlendirilmiş tamsayılar (ondalık noktaları olmadan) içerdiği sürece, grep olmadan bile yapabileceğimize dikkat çekti - sadece saf bash:

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

Bu evens, tüm olasılıkları filtreleyerek ve ardından listenin uzunluğunu döndürerek çağrılan bir liste oluşturarak çalışır .


İlginç bir yaklaşım. Tabii ki bu 3.0çift ​​sayı olarak sayılır ; soru, sayıların ne biçim alacağı konusunda kesin değildi.
G-Man, 'Monica'yı Yeniden

@ G-Man bash kayan nokta aritmetiği desteklemediğinden, tamsayıları kabul etmek güvenlidir
muru

Soru “çift” (ve dolaylı olarak “tek”) rakamlardan bahsettiğinden, tamsayılardan bahsettiğini varsaymak biraz güvenlidir. Kullanıcının kullanması beklenen aracın yeteneklerinden ve sınırlamalarından soru hakkında sonuçlar çıkarmanın ne kadar geçerli olduğunu görmüyorum. Unutmayın: soru bunun bir ödev olduğunu söylüyor - sanırım okul için, çünkü bu bir işten olamayacak kadar önemsiz. Okul öğretmenleri çılgınca şeyler bulurlar.
G-Man

2
Boşluk içermeyen hiçbir öğenin boşluk içermediğini varsayabilirsek, Bash bir listeyi kendi başına filtreleyebilir: dizinin sonunda, bir dizi başlatıcısının içinde bir eşleşme gerektirecek ${/%/}şekilde @diziyi kullanarak boş dizeyle değiştirilen tek sayıları içeren arg listesini genişletin . Sayıyı yazdırın. : Tanımlar ve çalıştırmak için bir tek astar olarak foo(){ evens=( ${@/%*[13579]/} ); echo "${#evens[@]} even numbers"; printf "%s\n" "${evens[@]}"; }; foo 135 212 325 3 6 3 4 5 9 7 2 12310. Listenin hata ayıklama için yazdırılmasını içerir. Baskılar 5 even numbers 212 6 4 2 12310(ayrı satırlarda)
Peter Cordes

1
Muhtemelen ZSH, yeni bir diziyi yeniden oluşturmak için kelime bölünmesine bağlı olarak, bir listeyi düzgün bir şekilde filtrelemek için bir şeye sahiptir (Biraz şaşırdım bash yapmadım; sadece her öğeye skaler dize genişletme şeyler uygulamak). Büyük listeler için, grepaslında daha hızlı olsaydı şaşırmazdım bash. Hmm, bu bash fonksiyonunu gönderebileceğim bir codegolf sorusu olup olmadığını merak ediyorum: P
Peter Cordes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.