Tại sao lại sử dụng && 75 lần nhanh hơn so với nếu fi fi và cách làm cho mã rõ ràng hơn


38

Tôi có mã làm việc sau đây:

largest_prime=1
for number_under_test in {1..100}
do
  is_prime=true
  factors=''
  for ((divider = 2; divider < number_under_test-1; divider++));
  do
    remainder=$(($number_under_test % $divider))
    [ $remainder == 0 ] && [ is_prime ] && is_prime=false && factors+=$divider' '
  done
  [ $is_prime == true ] && echo "${number_under_test} is prime!" || echo "${number_under_test} is NOT prime (factors= $factors)"  [ $is_prime == true ] && largest_prime=$number_under_test
done
printf "\nLargest Prime= $largest_prime\n"

Mã này chạy nhanh là 0,194 giây. Tuy nhiên tôi thấy && is_prime= falsehơi khó đọc và nó có thể nhìn (đến mắt chưa được huấn luyện) như thể nó đang được thử nghiệm chứ không phải là thiết lập như những gì nó làm. Vì vậy, tôi đã thử thay đổi &&thành một if...thenvà điều này hoạt động - nhưng chậm hơn 75 lần ở 14,48 giây. Đó là đáng chú ý nhất trên những con số cao hơn.

largest_prime=1
for number_under_test in {1..100}
do
  is_prime=true
  factors=''
  for ((divider = 2; divider < number_under_test-1; divider++));
  do  
    remainder=$(($number_under_test % $divider))
    if ([ $remainder == 0 ] && [ $is_prime == true ]); then
      is_prime=false
      factors+=$divider' '
    fi  
  done
  [ $is_prime == true ] && echo "${number_under_test} is prime!" || echo "${number_under_test} is NOT prime (factors= $factors)"  [ $is_prime == true ] && largest_prime=$number_under_test
done  
printf "\nLargest Prime= $largest_prime\n"

Có ai có được sự rõ ràng của khối mà không có sự chậm chạp?

Cập nhật (1/4/2015 10:40 sáng EST)

Phản hồi tuyệt vời! Bây giờ tôi đang sử dụng như sau. Có phản hồi nào khác không ?

largest_prime=1
separator=' '
for number_under_test in {1..100}; {
  is_prime=true
  factors=''
  for ((divider = 2; divider < (number_under_test/2)+1; divider++)) {
    remainder=$(($number_under_test % $divider))
    if [ $remainder == 0 ]; then
      is_prime=false
      factors+=$divider' '
    fi
  } 
  if $is_prime; then
    printf "\n${number_under_test} IS prime\n\n"
    largest_prime=$number_under_test
  else
    printf "${number_under_test} is NOT prime, factors are: "
    printf "$factors\n"
  fi
}
printf "\nLargest Prime= $largest_prime\n"

1
Trên một sidenote, chạy tập lệnh của bạn in nó Largest Prime= 100trên máy tính của tôi.
Giulio Muscarello

3
Ngoài ra, trên một sidenote, nếu bạn quan tâm đến hiệu quả, một cách nhỏ để cải thiện điều này sẽ chỉ lặp lại tối đa number_under_test/2thay vì tối đa number_under_test-1: Không có yếu tố nào của số n lớn hơn n / 2, vì vậy bạn vẫn sẽ tìm thấy tất cả các yếu tố cho số không nguyên tố bằng cách làm điều này. (Ngoài ra nếu bạn chỉ quan tâm đến việc kiểm tra tính nguyên thủy, thì việc lặp lại lên sqrt (n) là đủ, nhưng Bash không có chức năng tích hợp sẵn để tính căn bậc hai.)
Malte Skoruppa

Mờ, điểm tốt (+1). Sự thay đổi duy nhất là nó không hoạt động cho số 4 vì vậy tôi đã phải thực hiện nó (number_under_test/2)+1để cho phép điều đó
Michael Durrant

1
Trong phiên bản cập nhật của bạn, niềng răng {}không thực sự cần thiết sau khi các thenkhoản vì thenđã phục vụ như là một nhà điều hành nhóm (cùng với elif, elsehoặc fi). Trong thực tế, trong một số shell, bạn có thể viết, ví dụ, for i in 1 2 3; { echo $i; }không có dohoặc done.
Jonathan Leffler

1
+1 Jonathan, tôi đã thực hiện những thay đổi đó và cập nhật bản cập nhật
Michael Durrant

Câu trả lời:


66

Đó là bởi vì bạn đang sinh ra một lớp vỏ phụ mỗi lần:

if ([ $remainder == 0 ] && [ $is_prime == true ]); then

Chỉ cần xóa dấu ngoặc đơn

if [ $remainder == 0 ] && [ $is_prime == true ]; then

Nếu bạn muốn nhóm các lệnh, có cú pháp để thực hiện điều đó trong trình bao hiện tại :

if { [ $remainder == 0 ] && [ $is_prime == true ]; }; then

(dấu chấm phẩy là bắt buộc, xem hướng dẫn )

Lưu ý rằng [ is_prime ]không giống như [ $is_prime == true ]: bạn có thể viết đơn giản như vậy $is_prime(không có dấu ngoặc) sẽ gọi lệnh bash tích hợp truehoặc falselệnh.
[ is_prime ]là một thử nghiệm với một đối số, chuỗi "is_prime" - khi [được đưa ra một đối số duy nhất, kết quả là thành công nếu đối số không trống và chuỗi ký tự đó luôn không trống, do đó luôn luôn "đúng".

Để dễ đọc, tôi sẽ thay đổi dòng rất dài

[ $is_prime == true ] && echo "${number_under_test} is prime!" || echo "${number_under_test} is NOT prime (factors= $factors)"  [ $is_prime == true ] && largest_prime=$number_under_test

đến

if [ $is_prime == true ]; then
  echo "${number_under_test} is prime!"
else 
  echo "${number_under_test} is NOT prime (factors= $factors)"
  # removed extraneous [ $is_prime == true ] test that you probably
  # didn't notice off the edge of the screen
  largest_prime=$number_under_test
fi

Đừng đánh giá thấp khoảng trắng để cải thiện sự rõ ràng.


1
có một Typo - largest_prime=$number_under_testnên ở nhánh sau đó (lỗi tương tự là ở bản gốc)
JJoao

1
Cũng đáng lưu ý rằng trong bash, zsh, et al, [đang gọi một chương trình được gọi theo nghĩa đen [, trong khi [[được triển khai trong trình bao - do đó nó sẽ nhanh hơn. Hãy thử time for ((i = 0; $i < 1000; i++)); do [ 1 ]; donevà so sánh với [[. Xem câu hỏi SO này để biết thêm.
kirb

2
bash thực hiện [, đó là một nội dung. Từ dấu nhắc shell, gõ type -a [help [
glenn jackman

@glennjackman Wow; đã không nhận thức được điều đó. Tôi cho rằng đó vẫn là trường hợp vì which [vẫn trả lại /usr/bin/[. Tôi cũng chỉ nhận ra rằng tôi ngụ ý zsh là như nhau; Đối với tôi, điều đó cho tôi biết đó là một nội dung. Nhưng sau đó ... tại sao [[nhanh hơn?
kirb

2
@glennjackman command -vlà một whichlựa chọn tốt khác; xem thêm tại đây .
Abbafei

9

Tôi nghĩ rằng bạn đang làm việc quá sức với chức năng đó của bạn. Xem xét:

unset num div lprime; set -- "$((lprime=(num=(div=1))))"
while [     "$((     num += ! ( div *= ( div <= num   ) ) ))" -eq \
            "$((     num *=   ( div += 1 )   <= 101   ))" ]    && {
      set   "$(( ! ( num %      div )         * div   ))"     "$@"
      shift "$(( !    $1 +    ( $1 ==  1 )    *  $#   ))"
}; do [ "$div" -gt "$num" ] && echo "$*"      
done

Số học Shell khá có khả năng tự đánh giá các điều kiện nguyên. Nó hiếm khi cần quá nhiều bài kiểm tra và / hoặc bài tập bên ngoài. Cái này whilevòng lặp trùng lặp vòng lồng nhau của bạn khá tốt:

Tất nhiên, nó không in nhiều như vậy, tôi đã không viết nhiều như vậy, nhưng, ví dụ, đặt trần lên 16 thay vì 101 như được viết ở trên và ...

2
3
4 2
5
6 3 2
7
8 4 2
9 3
10 5 2
11
12 6 4 3 2
13
14 7 2
15 5 3

Nó chắc chắn đang làm việc. Và nó đòi hỏi rất ít khác để ước tính đầu ra của bạn:

...
do [ "$div" -eq "$num" ] && shift &&
   printf "$num ${1+!}= prime.${1+\t%s\t%s}\n" \
          "factors= $*"                        \
          "lprime=$(( lprime = $# ? lprime : num ))"
done

Chỉ làm điều đó chứ không phải echovà ...

1 = prime.
2 = prime.
3 = prime.
4 != prime.     factors= 2      lprime=3
5 = prime.
6 != prime.     factors= 3 2    lprime=5
7 = prime.
8 != prime.     factors= 4 2    lprime=7
9 != prime.     factors= 3      lprime=7
10 != prime.    factors= 5 2    lprime=7
11 = prime.
12 != prime.    factors= 6 4 3 2        lprime=11
13 = prime.
14 != prime.    factors= 7 2    lprime=13
15 != prime.    factors= 5 3    lprime=13

Điều này làm việc trong busybox. Nó rất di động, nhanh chóng và dễ sử dụng.

Vấn đề subshell của bạn sẽ xảy ra trong hầu hết các shell, nhưng, cho đến nay , hầu hết cấp tính trong một bashshell. Tôi xen kẽ làm

( [ "$div" -gt "$num" ] ) && ...

... Và cách tôi đã viết nó ở trên trong một số vỏ cho trần 101 và dashđã thực hiện nó mà không cần subshell trong 0,017 giây và với subshell trong 1,8 giây. busybox.149 và 2, zsh .149 và 4, bash.35 và 6, và ksh93trong .149 và .160. ksh93không rẽ nhánh cho các lớp con như các vỏ khác phải. Vì vậy, có lẽ vấn đề không phải là quá nhiều vì nó là vỏ .


Lợi thế của [ "$((...))" -eq "$((...))" ]hơn là (( (...) == (...) ))gì? Là cái sau ít di động?
ruakh

@ruakh - tính di động, tốc độ, độ tin cậy. [ "$((...))" -eq "$((...)) ]hoạt động trong các shell không mất 15 giây để chạy chương trình, và các chương trình khác thì không. Nếu lợi thế của cái này so với cái khác là đáng nghi ngờ, thì điều đó chỉ có thể mang lại lợi thế cho cái trước, điều đó có nghĩa là không bao giờ có một lý do tốt để sử dụng (( (...) == (...) )).
mikeerv

Xin lỗi, nhưng trả lời của bạn dường như cho rằng tôi đã có kiến ​​thức chi tiết về hỗ trợ shell cho (( ... )). Tôi hãnh diện, nhưng tôi làm không có kiến thức chi tiết. (Hãy nhớ rằng, tôi là người vừa hỏi nếu (( ... ))ít di động hơn.) Vì vậy, tôi thực sự không thể hiểu được câu trả lời của bạn. : - / Bạn có thể rõ ràng hơn một chút không?
ruakh

@ruakh - Tôi xin lỗi ... Tôi không thấy rằng bạn đang hỏi liệu nó có dễ mang theo hơn không, đó là cách nó có lợi thế - đó là lý do tại sao tôi trả lời về tính di động. Dù sao, "$((...))"được chỉ định POSIX và cái còn lại là một phần mở rộng shell. Vỏ POSIX có khả năng khá tốt. Thậm chí dashposhsẽ xử lý chính xác các kiểm tra chi nhánh như "$((if_true ? (var=10) : (var=5) ))"và luôn luôn chỉ định $varchính xác. busyboxphá vỡ ở đó - nó luôn luôn tránh cả hai bên bất kể $if_truegiá trị của nó.
mikeerv

@ruakh - trời ơi. Tôi phải nghỉ một chút hôm nay ... nó nói ngay đó ... là cái sau ít di động hơn ? Tôi đã không nhìn thấy điều đó trước đây, tôi đoán ...?
mikeserv
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.