ความกะทัดรัดเทียบกับการอ่านได้: พื้นกลาง
ในขณะที่คุณได้เห็นปัญหานี้ยอมรับว่าการแก้ปัญหาที่มีในระดับปานกลางยาวและค่อนข้างซ้ำ แต่สามารถอ่านได้สูง ( terdon ของและAB ของคำตอบทุบตี) เช่นเดียวกับผู้ที่มีความสั้นมาก แต่ไม่ใช่งานง่ายและมากน้อยตนเอง documenting (ทิมหลามและทุบตีคำตอบและคำตอบของperlเกล็นแจ็คแมน) วิธีการทั้งหมดนี้มีค่า
นอกจากนี้คุณยังสามารถแก้ปัญหานี้ด้วยรหัสที่อยู่ตรงกลางระหว่างความกะทัดรัดและความสามารถในการอ่าน วิธีนี้เกือบจะอ่านได้เหมือนโซลูชันที่ยาวกว่าและมีความยาวใกล้กับโซลูชันลึกลับที่มีขนาดเล็ก
#!/usr/bin/env bash
read -erp 'Enter numeric grade (q to quit): '
case $REPLY in [qQ]) exit;; esac
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; exit; }
done
echo "Grade out of range."
ในโซลูชันทุบตีนี้ฉันได้รวมบรรทัดว่างไว้เพื่อปรับปรุงความสามารถในการอ่าน แต่คุณสามารถลบออกได้หากคุณต้องการให้สั้นลง
บรรทัดที่ว่างเปล่ารวมนี้เป็นจริงเพียงเล็กน้อยสั้นกว่าcompactified แตกต่างยังคงสามารถอ่านได้สวยของวิธีการแก้ปัญหาของทุบตี AB ข้อได้เปรียบหลักเหนือวิธีการคือ:
- มันใช้งานง่ายมากขึ้น
- ง่ายกว่าในการเปลี่ยนขอบเขตระหว่างคะแนน (หรือเพิ่มเกรดเพิ่มเติม)
- จะรับอินพุตโดยอัตโนมัติด้วยช่องว่างนำหน้าและต่อท้าย (ดูด้านล่างสำหรับคำอธิบายวิธีการ
(( ))ทำงาน)
ข้อดีทั้งสามข้อนี้เกิดขึ้นเนื่องจากวิธีนี้ใช้อินพุตของผู้ใช้เป็นข้อมูลตัวเลขมากกว่าโดยการตรวจสอบตัวเลขส่วนประกอบด้วยตนเอง
มันทำงานอย่างไร
- อ่านอินพุตจากผู้ใช้ ปล่อยให้พวกเขาใช้ปุ่มลูกศรเพื่อเลื่อนไปมาในข้อความที่พวกเขาป้อน (
-e) และอย่าตีความ\ว่าเป็นอักขระยกเว้น ( -r)
สคริปต์นี้ไม่ใช่โซลูชันที่มีคุณสมบัติหลากหลาย - ดูด้านล่างสำหรับการปรับแต่ง - แต่คุณสมบัติที่มีประโยชน์เหล่านั้นทำให้มีความยาวได้สองอักขระเท่านั้น ฉันแนะนำให้ใช้เสมอ-rกับreadเว้นแต่คุณจะรู้ว่าคุณต้องปล่อยให้ผู้ใช้\หลบหนี
- หากผู้ใช้เขียน
qหรือQออกจาก
- สร้างอาเรย์แบบเชื่อมโยง ( ) เติมด้วยระดับตัวเลขสูงสุดที่เกี่ยวข้องกับแต่ละเกรดตัวอักษร
declare -A
- วนรอบคะแนนตัวอักษรจากต่ำสุดไปหาสูงสุดตรวจสอบว่าหมายเลขที่ผู้ใช้ให้นั้นต่ำพอที่จะตกอยู่ในช่วงตัวเลขของแต่ละเกรดของตัวอักษร
ด้วยการประเมินผลคณิตศาสตร์ชื่อตัวแปรไม่จำเป็นต้องขยายได้ด้วย(( )) $(ในสถานการณ์อื่น ๆ ส่วนใหญ่หากคุณต้องการใช้ค่าของตัวแปรแทนชื่อคุณต้องทำสิ่งนี้ )
- ถ้ามันอยู่ในช่วงที่พิมพ์เกรดและทางออก
เพื่อความกระชับผมใช้ลัดวงจรและผู้ประกอบการ ( &&) มากกว่า-ifthen
- หากลูปเสร็จสิ้นและไม่ได้จับคู่ช่วงให้ถือว่าจำนวนที่ป้อนนั้นสูงเกินไป (มากกว่า 100) และบอกผู้ใช้ว่าอยู่นอกช่วง
วิธีนี้ทำงานด้วยการป้อนข้อมูลที่แปลก
เช่นเดียวกับโซลูชันระยะสั้นอื่นๆ ที่โพสต์สคริปต์นั้นไม่ตรวจสอบอินพุตก่อนสมมติว่าเป็นตัวเลข การประเมินผลเลขคณิต ( (( ))) โดยอัตโนมัติแถบชั้นนำและต่อท้ายช่องว่างเพื่อที่ว่าไม่มีปัญหา แต่:
- ข้อมูลที่ไม่เหมือนกับตัวเลขจะถูกตีความว่าเป็น 0
- ด้วยอินพุตที่ดูเหมือนหมายเลข (เช่นถ้าขึ้นต้นด้วยหลัก) แต่มีอักขระที่ไม่ถูกต้องสคริปต์จะปล่อยข้อผิดพลาด
- การป้อนข้อมูล Multi-หลักเริ่มต้นด้วยการ
0ถูกตีความว่าเป็นในฐานแปด ตัวอย่างเช่นสคริปต์จะบอกคุณ 77 คือ C ในขณะที่ 077 เป็น D แม้ว่าผู้ใช้บางรายอาจต้องการสิ่งนี้ส่วนใหญ่อาจไม่และอาจทำให้เกิดความสับสน
- ในด้านบวกเมื่อได้รับนิพจน์ทางคณิตศาสตร์สคริปต์นี้จะลดความซับซ้อนของมันโดยอัตโนมัติและกำหนดเกรดตัวอักษรที่เกี่ยวข้อง ตัวอย่างเช่นมันจะบอกคุณ 320/4 เป็น B
เวอร์ชันที่ขยายและโดดเด่นอย่างสมบูรณ์
ด้วยเหตุผลเหล่านั้นคุณอาจต้องการใช้บางสิ่งบางอย่างเช่นสคริปต์ที่ขยายนี้ซึ่งตรวจสอบเพื่อให้แน่ใจว่าอินพุตนั้นดีและรวมถึงการปรับปรุงอื่น ๆ
#!/usr/bin/env bash
shopt -s extglob
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
while read -erp 'Enter numeric grade (q to quit): '; do
case $REPLY in # allow leading/trailing spaces, but not octal (e.g. "03")
*( )@([1-9]*([0-9])|+(0))*( )) ;;
*( )[qQ]?([uU][iI][tT])*( )) exit;;
*) echo "I don't understand that number."; continue;;
esac
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
done
echo "Grade out of range."
done
นี่ยังเป็นโซลูชันที่กะทัดรัด
คุณลักษณะนี้เพิ่มอะไรบ้าง
จุดสำคัญของสคริปต์ที่ขยายนี้คือ:
- การตรวจสอบอินพุต สคริปต์ของ terdon ตรวจสอบอินพุตด้วยดังนั้นฉันจึงแสดงอีกวิธีหนึ่งซึ่งเสียสละบางอย่างสั้น ๆ แต่มีประสิทธิภาพมากขึ้นทำให้ผู้ใช้ป้อนช่องว่างนำหน้าและต่อท้ายและปฏิเสธที่จะอนุญาตให้มีการแสดงออกที่อาจหรือไม่ตั้งใจให้เป็นฐานแปด (เว้นแต่เป็นศูนย์) .
if [[ ! $response =~ ^[0-9]*$ ]] ...
- ฉันใช้
caseกับการขยายแบบวงกลมแทนการ[[ใช้ตัวดำเนินการ=~ จับคู่นิพจน์ทั่วไป (ตามคำตอบของ terdon ) ฉันทำอย่างนั้นเพื่อแสดงว่า (และวิธี) มันก็สามารถทำได้เช่นกัน Globs และ regexps เป็นสองวิธีในการระบุรูปแบบที่ตรงกับข้อความและทั้งสองวิธีนี้ใช้ได้สำหรับแอปพลิเคชันนี้
- เช่นเดียวกับสคริปต์ทุบตีของ ABฉันได้ล้อมรอบทั้งหมดไว้ในลูปด้านนอก (ยกเว้นการสร้าง
cutoffsอาร์เรย์เริ่มต้น) มันขอหมายเลขและให้คะแนนตัวอักษรที่เกี่ยวข้องตราบใดที่อินพุตเทอร์มินัลพร้อมใช้งานและผู้ใช้ไม่ได้บอกให้เลิก ตัดสินโดยdo... doneรอบรหัสในคำถามของคุณดูเหมือนว่าคุณต้องการ
- เพื่อให้เลิกง่ายฉันยอมรับตัวแปรที่ไม่ต้องตรงตามตัวพิมพ์เล็ก
qและquitใหญ่
สคริปต์นี้ใช้โครงสร้างบางอย่างที่อาจไม่คุ้นเคยกับสามเณร พวกเขามีรายละเอียดด้านล่าง
คำอธิบาย: การใช้ continue
เมื่อฉันต้องการข้ามส่วนที่เหลือของร่างกายด้านนอก whileลูปฉันใช้continueคำสั่ง สิ่งนี้จะนำมันกลับขึ้นไปด้านบนของลูปเพื่ออ่านอินพุตเพิ่มเติมและรันการวนซ้ำอีกครั้ง
ครั้งแรกที่ฉันทำสิ่งนี้เฉพาะลูปที่ฉันอยู่คือwhileลูปภายนอกดังนั้นฉันจึงสามารถโทรได้continueโดยไม่มีการโต้แย้ง (ฉันอยู่ในcaseโครงสร้าง แต่ไม่ส่งผลกระทบต่อการทำงานของbreakหรือcontinue )
*) echo "I don't understand that number."; continue;;
อย่างไรก็ตามครั้งที่สองฉันอยู่ในforวงด้านในซึ่งตัวมันเองซ้อนอยู่ภายในwhileวงด้านนอก ถ้าฉันใช้continueโดยไม่มีข้อโต้แย้งสิ่งนี้จะเทียบเท่าcontinue 1และจะดำเนินการต่อforวงในแทนwhileวงด้านนอก
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
ดังนั้นในกรณีนี้ฉันใช้continue 2เพื่อค้นหา bash และวนลูปที่สองต่อไปแทน
คำอธิบาย: caseป้ายกำกับที่มี Globs
ฉันไม่ได้ใช้caseเพื่อหาตัวเลขตัวอักษรเกรดถังขยะที่ตกอยู่ใน (เช่นในคำตอบ bash ของ AB ) แต่ฉันจะใช้caseเพื่อตัดสินใจว่าควรพิจารณาการป้อนข้อมูลของผู้ใช้หรือไม่:
- หมายเลขที่ถูกต้อง
*( )@([1-9]*([0-9])|+(0))*( )
- คำสั่งเลิก
*( )[qQ]?([uU][iI][tT])*( )
- สิ่งอื่นใด (และการป้อนข้อมูลที่ไม่ถูกต้อง)
*
เหล่านี้เป็นglobs เปลือก
- แต่ละคนตามมาด้วยสิ่ง
)ที่ไม่ตรงกันโดยการเปิดใด ๆ(ซึ่งก็คือcaseไวยากรณ์ของการแยกรูปแบบจากคำสั่งที่ทำงานเมื่อมีการจับคู่
;;เป็นcaseไวยากรณ์ของการระบุจุดสิ้นสุดของคำสั่งที่จะเรียกใช้สำหรับการจับคู่กรณี paticular (และไม่ควรทดสอบกรณีที่ตามมาหลังจากเรียกใช้)
เชลล์แบบปกติจะจัดให้มี*อักขระเท่ากับศูนย์หรือมากกว่า?เพื่อจับคู่อักขระหนึ่งตัวและคลาส / ช่วงอักขระใน[ ]วงเล็บ แต่ฉันกำลังใช้การขยายแบบวงกลมซึ่งไปไกลกว่านั้น การขยายแบบวงกลมจะถูกเปิดใช้งานตามค่าเริ่มต้นเมื่อใช้bashแบบโต้ตอบ แต่จะถูกปิดใช้งานตามค่าเริ่มต้นเมื่อเรียกใช้สคริปต์ shopt -s extglobคำสั่งที่ด้านบนของสคริปต์เปลี่ยนมันใน
คำอธิบาย: ขยาย Globbing
*( )@([1-9]*([0-9])|+(0))*( )ซึ่งจะตรวจสอบอินพุตตัวเลขตรงกับลำดับของ:
- เป็นศูนย์หรือมากกว่าช่องว่าง (
*( )) *( )การแข่งขันสร้างศูนย์หรือมากกว่าของรูปแบบในวงเล็บซึ่งนี่เป็นเพียงพื้นที่
จริง ๆ แล้วมีช่องว่างแนวนอนช่องว่างและแท็บสองประเภทและบ่อยครั้งที่ต้องการจับคู่แท็บด้วยเช่นกัน แต่ฉันไม่ต้องกังวลเกี่ยวกับเรื่องนี้ที่นี่เพราะสคริปต์นี้เขียนขึ้นสำหรับการป้อนข้อมูลด้วยตนเองและการ-eตั้งค่าสถานะเพื่อreadเปิดใช้งาน GNU readline นี่คือเพื่อให้ผู้ใช้สามารถย้ายไปมาในข้อความของพวกเขาด้วยปุ่มลูกศรซ้ายและขวา แต่มันมีผลข้างเคียงของการป้องกันโดยทั่วไปแท็บจากการป้อนอย่างแท้จริง
- หนึ่งเหตุการณ์ (
@( )) ของทั้ง ( |):
- ตัวเลขที่ไม่ใช่ศูนย์ (
[1-9]) ตามด้วยศูนย์หรือมากกว่า (*( ) ) ของหลักใด ๆ ( [0-9])
- หนึ่งหรือมากกว่า (
+( )) 0ของ
- เป็นศูนย์หรือมากกว่าเว้นวรรค (
*( )) อีกครั้ง
*( )[qQ]?([uU][iI][tT])*( )ซึ่งตรวจสอบคำสั่ง quitตรงกับลำดับของ:
- เป็นศูนย์หรือมากกว่าช่องว่าง (
*( ))
qหรือQ( [qQ])
- เลือกได้ - เช่นศูนย์หรือหนึ่งเหตุการณ์ (
?( )) - จาก:
uหรือU( [uU]) ตามด้วยiหรือI( [iI]) ตามด้วยtหรือT( [tT])
- เป็นศูนย์หรือมากกว่าเว้นวรรค (
*( )) อีกครั้ง
Variant: การตรวจสอบความถูกต้องของอินพุตด้วยนิพจน์ปกติเพิ่มเติม
หากคุณต้องการที่จะทดสอบการป้อนข้อมูลของผู้ใช้กับการแสดงออกปกติมากกว่า shell glob คุณอาจต้องการใช้รุ่นนี้ซึ่งใช้งานได้เหมือนกัน แต่ใช้[[และ=~(เช่นในคำตอบของ Terdon ) แทนcaseและขยายกลม
#!/usr/bin/env bash
shopt -s nocasematch
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
while read -erp 'Enter numeric grade (q to quit): '; do
# allow leading/trailing spaces, but not octal (e.g., "03")
if [[ ! $REPLY =~ ^\ *([1-9][0-9]*|0+)\ *$ ]]; then
[[ $REPLY =~ ^\ *q(uit)?\ *$ ]] && exit
echo "I don't understand that number."; continue
fi
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
done
echo "Grade out of range."
done
ข้อดีที่เป็นไปได้ของวิธีนี้คือ:
ในกรณีพิเศษนี้ไวยากรณ์นั้นง่ายกว่าเล็กน้อยอย่างน้อยก็ในรูปแบบที่สองซึ่งฉันจะตรวจสอบคำสั่ง quit นี่เป็นเพราะฉันสามารถตั้งค่าnocasematchตัวเลือกของเชลล์แล้วทุกกรณีของqและquitครอบคลุมโดยอัตโนมัติ
นั่นคือสิ่งที่shopt -s nocasematchคำสั่งทำ shopt -s extglobคำสั่งที่ถูกละไว้เป็น globbing ไม่ได้ใช้ในรุ่นนี้
ทักษะการแสดงออกปกติเป็นเรื่องธรรมดามากกว่าความสามารถใน extglobs ของ bash
คำอธิบาย: นิพจน์ปกติ
สำหรับรูปแบบที่ระบุทางด้านขวาของ=~โอเปอเรเตอร์นี่คือวิธีการที่นิพจน์ทั่วไปทำงาน
^\ *([1-9][0-9]*|0+)\ *$ซึ่งจะตรวจสอบอินพุตตัวเลขตรงกับลำดับของ:
- จุดเริ่มต้น - คือขอบซ้าย - ของบรรทัด (
^)
*ช่องว่างเป็นศูนย์หรือมากกว่า ( , postfix ที่ใช้) ตามปกติแล้วพื้นที่ไม่จำเป็นต้องมีการ\หลีกเลี่ยงในนิพจน์ปกติ แต่จำเป็นต้องมี[[เพื่อป้องกันข้อผิดพลาดทางไวยากรณ์
- สตริงย่อย (
( )) ที่เป็นหนึ่งหรืออื่น ๆ ( |) ของ:
[1-9][0-9]*: ตัวเลขที่ไม่ใช่ศูนย์ ( [1-9]) ตามด้วยศูนย์หรือมากกว่า ( *, postfix ที่ใช้) ของตัวเลขใด ๆ ( [0-9])
0+หนึ่งหรือมากกว่า ( +, postfix ประยุกต์) 0ของ
- ช่องว่างเป็นศูนย์หรือมากกว่า (
\ * ) เช่นก่อน
- จุดสิ้นสุด - เช่น, ขอบด้านขวา - ของเส้น (
$)
ต่างจากcaseป้ายกำกับที่ตรงกับนิพจน์ทั้งหมดที่ถูกทดสอบ=~ส่งคืนค่าจริงหากส่วนใดส่วนหนึ่งของนิพจน์ทางซ้ายตรงกับรูปแบบที่กำหนดให้เป็นนิพจน์ทางขวา นี่คือสาเหตุที่^และ$anchors ระบุจุดเริ่มต้นและจุดสิ้นสุดของบรรทัดที่นี่และไม่สอดคล้อง syntactically กับสิ่งที่ปรากฏในวิธีการที่มีcaseและ extglobs
วงเล็บที่มีความจำเป็นเพื่อให้^และ$ผูกไว้กับความร้าวฉานของและ[1-9][0-9]* 0+มิฉะนั้นจะเป็นการแยก^[1-9][0-9]*และ0+$และจับคู่อินพุตใด ๆ ที่เริ่มต้นด้วยตัวเลขที่ไม่ใช่ศูนย์หรือลงท้ายด้วย0(หรือทั้งสองอย่างซึ่งอาจยังคงมีตัวเลขที่ไม่ใช่ตัวเลขอยู่)
^\ *q(uit)?\ *$ซึ่งตรวจสอบคำสั่ง quitตรงกับลำดับของ:
- จุดเริ่มต้นของบรรทัด (
^ )
- ช่องว่างเป็นศูนย์หรือมากกว่า (
\ *ดูคำอธิบายข้างต้น)
qตัวอักษร หรือQตั้งแต่shopt nocasematchเปิดใช้งาน
- เลือกได้ - เช่นศูนย์หรือหนึ่งเหตุการณ์ (postfix
?) - ของสตริงย่อย ( ( )):
uตามด้วยตามด้วยi tหรือเนื่องจากshopt nocasematchเปิดใช้งานuอาจเป็นU; อิสระiอาจจะI; และเป็นอิสระ, อาจจะt T(นั่นคือความเป็นไปได้ไม่จำกัดuitและUIT.)
- ศูนย์หรือมากกว่าช่องว่างอีกครั้ง (
\ *)
- จุดสิ้นสุดของบรรทัด (
$)