Помилка функції оболонки для підрахунку парних чисел


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
}

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/bashUbuntu немає . Його/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
    

    (( ... ))- це арифметичний контекст . Це корисніше, ніж [[ ... ]]для виконання арифметичних перевірок, і крім того, ви можете опустити $змінні раніше (що полегшує читання, 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 зазначив, що ми можемо це зробити навіть без греппу - просто чистий баш, доки список вхідних даних містить просто добре сформовані цілі числа (без десяткових знаків):

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

Це працює, створюючи список, викликаний evensшляхом фільтрації всіх шансів, а потім повернення довжини цього списку.


Цікавий підхід. Звичайно, це буде вважатися 3.0парним числом; питання не було точним щодо того, яку форму будуть приймати цифри.
G-Man каже: "Відновіть Моніку"

@ G-Man, оскільки bash не підтримує арифметику з плаваючою комою, можна вважати цілі числа
muru

Оскільки питання зазначає "парні" (і неявно "непарні" числа), можна вважати, що мова йде про цілі числа. Я не бачу, наскільки справедливо робити висновки щодо питання з можливостей та обмежень інструменту, яким користувач повинен використовувати. Пам'ятайте: у питанні сказано, що це завдання - я думаю, для школи, тому що це занадто банально, щоб бути з роботи. Шкільні вчителі придумують деякі шалені речі.
G-Man каже: "Відновіть Моніку"

2
Bash може самостійно фільтрувати список, якщо ми можемо припустити, що жодні елементи не містять пробілу: розгорніть список аргументів непарними номерами, заміненими на порожній рядок, використовуючи ${/%/}в @масиві, щоб вимагати збігу в кінці рядка, всередині ініціалізатора масиву. Роздрукуйте кількість. Як однострочнікі визначити і запустити його: 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 має щось належним чином фільтрувати список, замість того, щоб залежати від розбиття слів, щоб відновити новий масив (я трохи здивований, баш не став; лише застосовуючи скалярні розширення для кожного елемента). Для великих списків я не здивуюсь, якби grepнасправді швидше bash. Хм, мені цікаво, чи є питання про кодовий гольф, де я можу розмістити цю функцію bash: P
Пітер Кордес,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.