Sai lầm trong hàm shell để đếm số chẵn


12

Đối với một bài tập tôi phải viết một hàm in số lượng các số chẵn khi được cung cấp với một chuỗi các số.

Tôi đã sử dụng đoạn mã tôi đã sử dụng cho lần gán trước (để in 1khi số chẵn và 0khi số lẻ)

Vấn đề của tôi bây giờ là chức năng của tôi tiếp tục in 0. Tôi đang làm gì sai?

Đây là kịch bản của tôi:

#!/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
viết vào shebang của bạn '#! / usr / bin / bash -x' sau đó bạn sẽ thấy chính xác điều gì sẽ xảy ra.
Ziazis

+1 để nói với chúng tôi đó là bài tập về nhà - và vì đã làm việc đủ chăm chỉ để xứng đáng được giúp đỡ.
Joe

Câu trả lời:


20

Bạn chỉ quên thay thế $#bằng ( $) elementtrong forvòng lặp:

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
}

Bây giờ để kiểm tra chức năng:

$ 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

@dPlay đã tìm thấy vấn đề cốt lõi, tôi sẽ đưa ra một số đánh giá mã:

  1. Các shebang: Không có /usr/bin/bashtrong Ubuntu. Đó là /bin/bash.
  2. Thật tốt khi bạn đã khai báo sum localvà tránh làm ô nhiễm không gian tên biến ngoài hàm. Ngoài ra, bạn có thể khai báo nó một biến số nguyên bằng cách sử dụng -itùy chọn:

    local -i sum=0
  3. Luôn trích dẫn các biến của bạn (và tham số)! Không cần thiết trong kịch bản này, nhưng một thói quen rất tốt để tham gia:

    for element in "$@"
    do

    Điều đó nói rằng, bạn có thể bỏ qua in "$@"ở đây:

    for element
    do

    Khi in <something>không được đưa ra, forvòng lặp ngầm lặp lại các đối số. Điều này có thể tránh những sai lầm như quên các trích dẫn.

  4. Không cần phải tính toán và sau đó kiểm tra kết quả. Bạn có thể trực tiếp thực hiện phép tính trong if:

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

    (( ... ))bối cảnh số học . Nó hữu ích hơn so [[ ... ]]với việc thực hiện kiểm tra số học và ngoài ra, bạn có thể bỏ qua các $biến trước (giúp dễ đọc hơn, IMHO).

  5. Nếu bạn chuyển phần kiểm tra chẵn thành một chức năng riêng biệt, nó có thể cải thiện khả năng đọc và tái sử dụng:

    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
Nếu bạn đang tìm kiếm một cơ thể vòng lặp sử dụng sum=$((sum + 1 - element % 2)).
David Foerster

1
@DavidFoerster Terser và ít đọc hơn. ;-)
Konrad Rudolph

@DavidFoerster: Tôi có cùng suy nghĩ rằng bạn chỉ nên sử dụng kết quả mod-2 trực tiếp. (Hoặc tốt hơn, &1để kiểm tra bit thấp nếu bạn dễ đọc hơn). Nhưng chúng ta có thể làm cho nó dễ đọc hơn: sum=$((sum + !(element&1) ))sử dụng nghịch đảo boolean thay vì +1 - condition. Hoặc chỉ đếm các phần tử lẻ với ((odd += element&1)), và ở cuối in echo $(($# - element)), bởi vì even = total - odd.
Peter Cordes

Công việc tốt, và tốt để chỉ ra những điều nhỏ mà người mới bắt đầu bỏ lỡ, ví dụ local -isum++.
Paddy Landau

4

Tôi không chắc chắn nếu bạn mở cho các giải pháp khác. Ngoài ra tôi không biết liệu bạn có thể sử dụng các tiện ích bên ngoài không, hoặc nếu bạn hoàn toàn bị giới hạn đối với các nội dung bash. Nếu bạn có thể sử dụng grep, ví dụ, chức năng của bạn có thể đơn giản hơn rất nhiều:

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

Điều này đặt mỗi số nguyên đầu vào trên một dòng riêng của nó, và sau đó sử dụng grepđể đếm các dòng kết thúc bằng một chữ số chẵn.


Cập nhật - @PeterCordes chỉ ra rằng chúng tôi thậm chí có thể thực hiện việc này mà không cần grep - chỉ cần bash thuần túy, miễn là danh sách đầu vào chỉ chứa các số nguyên được hình thành tốt (không có dấu thập phân):

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

Điều này hoạt động bằng cách tạo một danh sách được gọi evensbằng cách lọc ra tất cả các tỷ lệ cược, sau đó trả về độ dài của danh sách đó.


Cách tiếp cận thú vị. Tất nhiên điều này sẽ được tính 3.0là một số chẵn; câu hỏi không chính xác về hình thức của những con số.
G-Man nói 'Phục hồi Monica'

@ G-Man vì bash không hỗ trợ số học dấu phẩy động, nên an toàn khi giả sử số nguyên
muru

Vì câu hỏi đề cập đến các số chẵn, thậm chí là số lẻ (và, ngầm, số lẻ lẻ), nên có thể an toàn khi cho rằng nó nói về số nguyên. Tôi không thấy nó hợp lệ như thế nào khi đưa ra kết luận về câu hỏi từ khả năng và giới hạn của công cụ mà người dùng dự kiến ​​sẽ sử dụng. Hãy nhớ rằng: câu hỏi nói rằng đây là một bài tập - tôi đoán cho trường học, bởi vì đây là cách quá tầm thường đối với một công việc. Giáo viên của trường nghĩ ra một số điều điên rồ.
G-Man nói 'Phục hồi Monica'

2
Bash có thể tự lọc một danh sách nếu chúng ta có thể giả sử không có phần tử nào chứa khoảng trắng: mở rộng danh sách arg bằng các số lẻ được thay thế bằng chuỗi rỗng sử dụng ${/%/}trên @mảng để yêu cầu khớp ở cuối chuỗi, bên trong bộ khởi tạo mảng. In số đếm. Là một lớp lót để xác định và chạy nó : foo(){ evens=( ${@/%*[13579]/} ); echo "${#evens[@]} even numbers"; printf "%s\n" "${evens[@]}"; }; foo 135 212 325 3 6 3 4 5 9 7 2 12310. Bao gồm thực sự in danh sách để gỡ lỗi. In 5 even numbers 212 6 4 2 12310(trên các dòng riêng)
Peter Cordes

1
Có lẽ ZSH có một cái gì đó để lọc danh sách một cách chính xác, thay vì phụ thuộc vào việc tách từ để xây dựng lại một mảng mới (tôi hơi ngạc nhiên khi bash không; chỉ áp dụng công cụ mở rộng chuỗi vô hướng cho mọi phần tử). Đối với các danh sách lớn, tôi sẽ không ngạc nhiên nếu grepthực sự nhanh hơn bash. Hmm, tôi tự hỏi nếu có một câu hỏi về codegolf nơi tôi có thể đăng chức năng bash đó: P
Peter Cordes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.