Ошибка в функции оболочки для подсчета четных чисел

12

Для назначения я должен написать функцию, которая печатает количество четных чисел, когда предоставляется последовательность чисел.

Я использовал фрагмент кода, который использовал для предыдущего назначения (для печати, 1когда число было четным, а 0когда число было нечетным)

Моя проблема сейчас в том, что моя функция продолжает печатать 0. Что я делаю неправильно?

Вот мой сценарий:

#!/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
источник
2
напишите в своем заголовке «#! / usr / bin / bash -x», и вы увидите, что именно происходит.
Зиазис
+1 за то, что сказал нам, что это домашнее задание, и за то, что работал над этим достаточно усердно, чтобы заслуживать помощи
Джо

Ответы:

20

Вы просто забыли заменить $#на ( $) elementв forцикле:

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
}

Теперь для проверки функции:

$ 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 обнаружил основную проблему, я дам небольшой обзор кода:

  1. Шебанг: нет /usr/bin/bashв Ubuntu. Это /bin/bash.
  2. Хорошо, что вы объявили sum local, и избежали загрязнения пространства имен переменной вне функции. Кроме того, вы можете объявить его целочисленной переменной, используя -iопцию:

    local -i sum=0
  3. Всегда указывайте свои переменные (и параметры)! В этом сценарии нет необходимости, но есть очень хорошая привычка:

    for element in "$@"
    do

    Тем не менее, вы можете опустить in "$@"здесь:

    for element
    do

    Когда in <something>не задано, forцикл неявно зацикливается на аргументах. Это может избежать ошибок, таких как забывание цитат.

  4. Там нет необходимости рассчитывать, а затем проверить результат. Вы можете напрямую сделать расчет в if:

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

    (( ... ))это арифметический контекст . Это более полезно, чем [[ ... ]]для выполнения арифметических проверок, и, кроме того, вы можете опустить $переменные before (что упрощает чтение, IMHO).

  5. Если вы переместили четную часть в отдельную функцию, это может улучшить читаемость и возможность повторного использования:

    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
Если вы ищете более короткий цикл использования тела sum=$((sum + 1 - element % 2)).
Дэвид Фёрстер
1
@DavidFoerster Terser и менее читабельный. ;-)
Конрад Рудольф
@DavidFoerster: У меня была такая же мысль, что вы должны просто использовать результат mod-2 напрямую. (Или лучше &1проверить младший бит, если он более читабелен для вас). Но мы можем сделать его более читабельным: sum=$((sum + !(element&1) ))использовать вместо логического обратное выражение +1 - condition. Или просто посчитайте нечетные элементы с помощью ((odd += element&1)), а в конце напечатайте echo $(($# - element)), потому что even = total - odd.
Питер Кордес
Хорошая работа, и хорошо, чтобы указать на мелочи, которые пропускают начинающие, например, local -iи sum++.
Пэдди Ландау
4

Я не уверен, что вы открыты для других решений. Также я не знаю, можете ли вы использовать внешние утилиты или вы просто ограничены встроенными средствами bash. Если вы можете использовать grep, например, ваша функция может быть намного проще:

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

Это помещает каждое входное целое число в свою собственную строку, а затем использует grepдля подсчета строк, заканчивающихся четной цифрой.


Обновление - @PeterCordes указало, что мы можем даже сделать это без grep - просто чистый bash, при условии, что входной список содержит только правильно сформированные целые числа (без десятичных точек):

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

Это работает путем создания списка, который вызывается evensпутем фильтрации всех коэффициентов, а затем возвращается длина этого списка.

Цифровая травма
источник
Интересный подход. Конечно, это будет считаться 3.0четным числом; вопрос не был точным о том, какую форму примут числа.
G-Man говорит: «Восстанови Монику»
@ G-Man, так как bash не поддерживает арифметику с плавающей запятой, можно предположить, что целые числа
muru
Поскольку в вопросе упоминаются «четные» (и, косвенно, «нечетные») числа, можно с уверенностью предположить, что речь идет о целых числах. Я не понимаю, как правильно делать выводы по этому вопросу из возможностей и ограничений инструмента, который должен использовать пользователь. Помните: вопрос говорит, что это задание - я думаю, для школы, потому что это слишком тривиально, чтобы быть с работы. Школьные учителя придумывают некоторые безумные вещи.
G-Man говорит: «Восстановите Монику»
2
Bash может фильтровать список по себе , если мы не можем считать ни одного элемента содержат пробелы: расширение списка ARG с нечетными номерами заменены пустой строкой , используя ${/%/}на @массиве требуется соответствие в конце строки, внутри инициализатора массива. Распечатать счет. Как однострочник определить и запустить его: foo(){ evens=( ${@/%*[13579]/} ); echo "${#evens[@]} even numbers"; printf "%s\n" "${evens[@]}"; }; foo 135 212 325 3 6 3 4 5 9 7 2 12310. Включает в себя фактически печать списка для отладки. Отпечатки 5 even numbers 212 6 4 2 12310(на отдельных строках)
Питер Кордес
1
Вероятно, в ZSH есть что-то, что должно правильно фильтровать список, вместо того, чтобы полагаться на разбиение слов для перестройки нового массива (я был немного удивлен, что bash этого не сделал; применение только скалярного расширения строк к каждому элементу). Для больших списков я не удивлюсь, если бы на grepсамом деле был быстрее, чем bash. Хм, интересно, есть ли вопрос Codegolf, где я мог бы опубликовать эту функцию bash: P
Питер Кордес