เหตุใดการใช้ && 75 ครั้งเร็วกว่าถ้า ... fi และวิธีทำให้โค้ดชัดเจนขึ้น


38

ฉันมีรหัสการทำงานต่อไปนี้:

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"

รหัสนี้ทำงานอย่างรวดเร็วคือ 0.194 วินาที อย่างไรก็ตามฉันพบว่า&& is_prime= falseอ่านยากนิดหน่อยและมันอาจดู (กับตาที่ไม่ได้รับการฝึกฝน) ราวกับว่ามันถูกทดสอบมากกว่าที่จะถูกตั้งค่าซึ่งเป็นสิ่งที่มันทำ ดังนั้นฉันจึงลองเปลี่ยนค่า&&เป็น a if...thenและทำงานได้ - แต่ช้าลง 75 เท่าเวลา 14.48 วินาที มันเห็นได้ชัดเจนที่สุดในจำนวนที่สูงขึ้น

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"

มีใครบ้างที่จะต้องมีความชัดเจนของบล็อกโดยไม่มีความช้า?

อัปเดต (1/4/2015 10:40 น. EST)

ข้อเสนอแนะที่ยอดเยี่ยม! ตอนนี้ฉันใช้สิ่งต่อไปนี้ ข้อเสนอแนะอื่น ๆ ?

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
บน sidenote การเรียกใช้สคริปต์ของคุณจะพิมพ์Largest Prime= 100ลงบนคอมพิวเตอร์ของฉัน
Giulio Muscarello

3
นอกจากนี้ใน sidenote หากคุณสนใจในประสิทธิภาพวิธีหนึ่งที่น่าสนใจในการปรับปรุงสิ่งนี้คือการทำซ้ำไปจนถึงnumber_under_test/2แทนที่จะเป็นnumber_under_test-1: ไม่มีตัวประกอบของตัวเลข n มากกว่า n / 2 ดังนั้นคุณจะยังคงพบทั้งหมด ปัจจัยสำหรับตัวเลขที่ไม่สำคัญโดยการทำเช่นนี้ (และถ้าคุณสนใจที่จะทดสอบความเป็นเอกสิทธิ์เท่านั้นก็เพียงพอที่จะทำซ้ำได้ถึง sqrt (n) แต่ Bash ไม่มีฟังก์ชันในตัวเพื่อคำนวณรากที่สองต่อไป)
Malte Skoruppa

เคลือบจุดดี (+1) การเปลี่ยนแปลงเพียงอย่างเดียวนั้นไม่ได้ผลกับหมายเลข 4 ดังนั้นฉันจึงต้อง(number_under_test/2)+1อนุญาตให้ทำเช่นนั้น
Michael Durrant

1
ในรุ่นที่อัปเดตของคุณวงเล็บ{}ไม่จำเป็นจริงๆหลังจากที่thenข้อเพราะthenทำหน้าที่อยู่แล้วเป็นผู้ประกอบการจัดกลุ่ม (พร้อมด้วยelif, elseหรือfi) ในความเป็นจริงในเปลือกหอยบางอย่างที่คุณสามารถเขียนยกตัวอย่างเช่นfor i in 1 2 3; { echo $i; }ไม่มีหรือdo done
Jonathan Leffler

1
+1 โจนาธานฉันทำการเปลี่ยนแปลงเหล่านั้นและอัปเดตการอัปเดต
Michael Durrant

คำตอบ:


66

นั่นเป็นเพราะคุณวางไข่ sub-shell ทุกครั้ง:

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

เพียงแค่ลบวงเล็บ

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

หากคุณต้องการจัดกลุ่มคำสั่งมีไวยากรณ์ที่ต้องทำในเชลล์ปัจจุบัน :

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

(จำเป็นต้องใช้เครื่องหมายอัฒภาคต่อท้ายดูคู่มือ )

โปรดทราบว่า[ is_prime ]ไม่เหมือนกับ[ $is_prime == true ]: คุณสามารถเขียนเป็นเพียง$is_prime(โดยไม่ต้องวงเล็บ) ซึ่งจะเรียกใช้ในตัวtrueหรือfalseคำสั่งทุบตี
[ is_prime ]เป็นการทดสอบที่มีอาร์กิวเมนต์หนึ่งสตริง "is_prime" - เมื่อ[ได้รับอาร์กิวเมนต์เดี่ยวผลลัพธ์จะสำเร็จหากอาร์กิวเมนต์ไม่ว่างเปล่าและสตริงตัวอักษรนั้นจะไม่ว่างเสมอดังนั้นจึงเป็น "จริง" เสมอ

เพื่อความสะดวกในการอ่านฉันจะเปลี่ยนบรรทัดที่ยาวมาก

[ $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

ไปยัง

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

อย่าประมาทช่องว่างเพื่อดูความชัดเจน


1
มีการสะกดผิด - largest_prime=$number_under_testควรจะอยู่ในสาขานั้น (ข้อผิดพลาดเดียวกันอยู่ในต้นฉบับ)
JJoao

1
นอกจากนี้ยังเป็นที่น่าสังเกตว่าใน bash, zsh, et al, [กำลังเรียกใช้โปรแกรมที่เรียกว่าแท้จริง[, ในขณะที่[[มีการใช้งานในเชลล์ - ดังนั้นมันจะเร็วขึ้น ลองและเปรียบเทียบกับtime for ((i = 0; $i < 1000; i++)); do [ 1 ]; done [[ดูคำถาม SO นี้สำหรับข้อมูลเพิ่มเติม
kirb

2
ทุบตีใช้[มันเป็น builtin จากพร้อมต์เชลล์พิมพ์type -a [และhelp [
glenn jackman

@glennjackman ว้าว; ไม่ทราบว่า ฉันคิดว่ามันยังคงเป็นกรณีเพราะwhich [ยังคงกลับ/usr/bin/[มา ฉันเพิ่งตระหนักว่าฉันบอกเป็นนัย ๆ ว่า zsh นั้นเหมือนกัน; สำหรับฉันที่จะบอกฉันว่ามันเป็น builtin แต่ ... ทำไม [[เร็วกว่า]
kirb

2
@glennjackman command -vเป็นอีกwhichทางเลือกที่ดี ดูที่นี่ด้วย
Abbafei

9

ฉันคิดว่าคุณกำลังทำงานหนักเกินไปกับหน้าที่ของคุณ พิจารณา:

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

การคำนวณทางคณิตศาสตร์ของเชลล์นั้นสามารถประเมินเงื่อนไขจำนวนเต็มได้ด้วยตัวของมันเอง มันไม่ค่อยต้องการการทดสอบมากเกินไปและ / หรือการมอบหมายภายนอก นี้เป็นหนึ่งในwhileห่วงซ้ำลูปซ้อนกันของคุณค่อนข้างดี:

แน่นอนว่ามันไม่ได้พิมพ์ออกมาเท่าไรนักฉันไม่ได้เขียนอะไรมากมายนัก แต่ยกตัวอย่างเช่นตั้งเพดานเป็น 16 แทนที่จะเป็น 101 เท่าที่เขียนไว้ด้านบนและ ...

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

มันทำงานได้แน่นอน และมันต้องการอย่างน้อยมากเพื่อประมาณผลลัพธ์ของคุณ:

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

แค่ทำอย่างนั้นมากกว่าechoและ ...

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

งานนี้busyboxมา มันพกพาได้รวดเร็วและใช้งานง่าย

ปัญหา subshell ของคุณจะเกิดขึ้นในเปลือกหอยมากที่สุด แต่มันคือโดยไกล , เฉียบพลันมากที่สุดในbashเปลือก ฉันสลับไปมาระหว่างทำ

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

... และวิธีที่ฉันเขียนไว้ข้างบนในหลาย ๆ shell สำหรับเพดาน 101 และdashทำได้โดยไม่ต้อง subshell ใน. 0.17 วินาทีและกับ subshell ใน 1.8 วินาที busybox.149 และ 2, zsh .149 และ 4, bash.35 และ 6 และksh93ใน. 149 และ. 160 ksh93ไม่แยกสำหรับ subshells ตามที่เชลล์อื่น ๆ ต้องทำ ดังนั้นอาจเป็นปัญหาไม่มาก subshell ในขณะที่มันเป็นเปลือก


อะไรคือข้อได้เปรียบของ[ "$((...))" -eq "$((...))" ]มากกว่า(( (...) == (...) ))? พกพาน้อยกว่านี้หรือไม่?
ruakh

@ruakh - การพกพาความเร็วความน่าเชื่อถือ [ "$((...))" -eq "$((...)) ]ทำงานในเชลล์ที่ไม่ต้องใช้เวลา 15 วินาทีในการรันโปรแกรมและอื่น ๆ ใช้ไม่ได้ หากประโยชน์จากหนึ่งไปอีกเป็นที่น่าสงสัยที่ทุกคนแล้วว่าสามารถให้ขอบกับอดีตซึ่งหมายความว่ามีความไม่(( (...) == (...) ))เป็นเหตุผลที่ดีที่จะใช้
mikeserv

ขออภัยดูเหมือนว่าคำตอบของคุณจะถือว่าฉันมีความรู้โดยละเอียดเกี่ยวกับการสนับสนุนของเชลล์(( ... ))แล้ว ผมปลื้ม แต่ผมไม่ได้มีความรู้รายละเอียด (จำไว้ว่าฉันเป็นคนที่ถามว่า(( ... ))พกพาได้น้อยลง) ดังนั้นฉันจึงไม่สามารถตอบความรู้สึกของคุณได้ : - / คุณชัดเจนขึ้นอีกเล็กน้อยหรือไม่?
ruakh

@ruakh - ฉันขอโทษ ... ฉันไม่เห็นว่าคุณกำลังถามว่ามันเป็นแบบพกพามากขึ้นหรือไม่มันเป็นข้อได้เปรียบ - ซึ่งเป็นเหตุผลที่ฉันตอบเกี่ยวกับการพกพา อย่างไรก็ตามที่"$((...))"เป็นPOSIX ระบุและอื่น ๆ ที่เป็นส่วนขยายของเชลล์ POSIX shells มีความสามารถพอสมควร แม้dashและposhถูกต้องจะจัดการทดสอบสาขาชอบ"$((if_true ? (var=10) : (var=5) ))"และมักจะกำหนด$varได้อย่างถูกต้อง busyboxพักที่นั่น - มันจะประเมินทั้งสองด้านเสมอโดยไม่คำนึงถึง$if_trueมูลค่าของ
mikeserv

@ruakh - โอ้มนุษย์ วันนี้ฉันต้องออกไปเล็กน้อย ... มันบอกว่ามี ... พกพาน้อยกว่านี้ไหม? ฉันไม่เคยเห็นแบบนั้นมาก่อนฉันเดาว่า ... ?
mikeserv
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.