Brevity vs. Readability: A Middle Ground
Seperti yang Anda lihat, masalah ini mengakui solusi yang cukup panjang dan agak berulang tetapi sangat mudah dibaca ( jawaban bash terdon dan AB ), serta solusi yang sangat pendek namun tidak intuitif dan jauh lebih sedikit mendokumentasikan diri sendiri (Tim python dan bash jawaban dan perl perl glenn jackman ). Semua pendekatan ini sangat berharga.
Anda juga dapat memecahkan masalah ini dengan kode di tengah kontinum antara kekompakan dan keterbacaan. Pendekatan ini hampir sama terbaca dengan solusi yang lebih panjang, dengan panjang yang lebih dekat dengan solusi esoterik yang kecil.
#!/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."
Dalam solusi bash ini, saya telah menyertakan beberapa baris kosong untuk meningkatkan keterbacaan, tetapi Anda dapat menghapusnya jika Anda menginginkannya lebih pendek.
Baris kosong disertakan, ini sebenarnya hanya sedikit lebih pendek dari sebuah compactified, varian masih cukup dibaca dari solusi pesta AB . Keuntungan utama metode ini adalah:
- Ini lebih intuitif.
- Lebih mudah untuk mengubah batas antara nilai (atau menambah nilai tambahan).
- Secara otomatis menerima input dengan spasi awal dan akhir (lihat di bawah untuk penjelasan tentang cara
(( ))kerjanya).
Ketiga keunggulan ini muncul karena metode ini menggunakan input pengguna sebagai data numerik daripada dengan secara manual memeriksa digit-konstituennya.
Bagaimana itu bekerja
- Baca input dari pengguna. Biarkan mereka menggunakan tombol panah untuk bergerak di dalam teks yang telah mereka masukkan (
-e) dan tidak menafsirkan \sebagai karakter melarikan diri ( -r).
Skrip ini bukan solusi kaya fitur - lihat di bawah untuk penyempurnaan - tetapi fitur yang berguna hanya membuatnya dua karakter lebih lama. Saya sarankan selalu gunakan -rdengan read, kecuali Anda tahu Anda harus membiarkan pasokan pengguna \lolos.
- Jika pengguna menulis
qatau Q, keluar.
- Buat array asosiatif ( ). Isi dengan nilai angka tertinggi yang terkait dengan setiap nilai huruf.
declare -A
- Lingkari nilai huruf dari terendah ke tertinggi, memeriksa apakah angka yang disediakan pengguna cukup rendah untuk masuk dalam rentang angka setiap huruf.
Dengan (( ))evaluasi aritmatika, nama variabel tidak perlu diperluas $. (Dalam sebagian besar situasi lain, jika Anda ingin menggunakan nilai variabel sebagai ganti namanya, Anda harus melakukan ini .)
- Jika jatuh dalam kisaran, cetak grade dan keluar .
Untuk singkatnya, saya menggunakan hubungan pendek dan operator ( &&) daripada if- then.
- Jika loop selesai dan tidak ada jangkauan yang cocok, anggap angka yang dimasukkan terlalu tinggi (lebih dari 100) dan beri tahu pengguna bahwa itu di luar jangkauan.
Bagaimana ini Berperilaku, dengan Masukan Aneh
Seperti solusi singkat lainnya yang dipasang, skrip itu tidak memeriksa input sebelum mengasumsikan bahwa itu adalah angka. Evaluasi aritmatika ( (( ))) secara otomatis menghapus spasi spasi awal dan akhir, jadi itu tidak masalah, tetapi:
- Input yang tidak terlihat seperti angka sama sekali ditafsirkan sebagai 0.
- Dengan input yang terlihat seperti angka (yaitu, jika dimulai dengan angka) tetapi berisi karakter yang tidak valid, skrip memancarkan kesalahan.
- Input multi-digit dimulai dengan
0yang ditafsirkan sebagai di oktal . Misalnya, skrip akan memberi tahu Anda 77 adalah C, sementara 077 adalah D. Meskipun beberapa pengguna mungkin menginginkan ini, kemungkinan besar tidak dan ini dapat menyebabkan kebingungan.
- Di sisi positifnya, ketika diberi ekspresi aritmatika, skrip ini secara otomatis menyederhanakannya dan menentukan tingkat huruf terkait. Misalnya, ia akan memberi tahu Anda 320/4 adalah B.
Versi Diperluas, Fitur Penuh
Karena alasan itu, Anda mungkin ingin menggunakan sesuatu seperti skrip yang diperluas ini, yang memeriksa untuk memastikan inputnya baik, dan menyertakan beberapa perangkat tambahan lainnya.
#!/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
Ini masih merupakan solusi yang cukup kompak.
Fitur apa yang ditambahkan ini?
Poin kunci dari skrip yang diperluas ini adalah:
- Validasi input. Script terdon memeriksa input dengan , jadi saya menunjukkan cara lain, yang mengorbankan beberapa keringkasan tetapi lebih kuat, memungkinkan pengguna untuk memasukkan spasi awal dan akhir dan menolak untuk mengizinkan ekspresi yang mungkin atau mungkin tidak dimaksudkan sebagai oktal (kecuali nol) .
if [[ ! $response =~ ^[0-9]*$ ]] ...
- Saya telah menggunakan
casedengan diperpanjang globbing bukan [[dengan =~ ekspresi yang cocok reguler operator (seperti dalam jawaban Terdon ini ). Saya melakukan itu untuk menunjukkan bahwa (dan bagaimana) itu juga dapat dilakukan dengan cara itu. Gumpalan dan regexps adalah dua cara menentukan pola yang cocok dengan teks, dan metode mana pun baik untuk aplikasi ini.
- Seperti skrip bash AB , saya telah menyertakan semuanya dalam lingkaran luar (kecuali pembuatan awal
cutoffsarray). Ia meminta angka dan memberikan nilai huruf yang sesuai selama input terminal tersedia dan pengguna belum menyuruhnya untuk berhenti. Dilihat oleh do... di donesekitar kode dalam pertanyaan Anda, sepertinya Anda menginginkannya.
- Agar mudah berhenti, saya menerima varian case-insensitive
qatau quit.
Script ini menggunakan beberapa konstruksi yang mungkin asing bagi pemula; mereka dirinci di bawah ini.
Penjelasan: Penggunaan continue
Ketika saya ingin melewati sisa tubuh whileloop luar , saya menggunakan continueperintah. Ini membawanya kembali ke atas loop, untuk membaca lebih banyak input dan menjalankan iterasi lain.
Pertama kali saya melakukan ini, satu-satunya loop saya di adalah whileloop luar , jadi saya bisa menelepon continuetanpa argumen. (Saya dalam casekonstruksi, tetapi itu tidak mempengaruhi operasi breakatau continue.)
*) echo "I don't understand that number."; continue;;
Namun, kedua kalinya saya berada di forloop dalam yang bersarang di dalam whileloop luar . Jika saya menggunakan continuetanpa argumen, ini akan sama dengan continue 1dan akan melanjutkan forloop dalam, bukan whileloop luar .
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
Jadi dalam hal itu, saya gunakan continue 2untuk membuat bash find dan melanjutkan loop kedua sebagai gantinya.
Penjelasan: caseLabel dengan Glob
Saya tidak menggunakan caseuntuk mencari tahu mana-huruf bin angka jatuh ke (seperti dalam AB jawaban pesta ). Tapi saya gunakan caseuntuk memutuskan apakah input pengguna harus dipertimbangkan:
- nomor yang valid,
*( )@([1-9]*([0-9])|+(0))*( )
- perintah keluar,
*( )[qQ]?([uU][iI][tT])*( )
- hal lain (dan dengan demikian input tidak valid),
*
Ini adalah shell shell .
- Setiap diikuti oleh
)yang tidak cocok dengan pembukaan apa pun (, yang merupakan casesintaks untuk memisahkan pola dari perintah yang dijalankan saat dicocokkan.
;;adalah casesintaks untuk menunjukkan akhir perintah untuk menjalankan untuk pertandingan kasus paticular (dan bahwa tidak ada kasus berikutnya harus diuji setelah menjalankannya).
Globase shell biasa menyediakan *untuk mencocokkan dengan nol atau lebih karakter, ?untuk mencocokkan dengan tepat satu karakter, dan kelas / rentang karakter dalam [ ]kurung. Tapi saya menggunakan globbing diperpanjang , yang melampaui itu. Perpanjangan globbing diaktifkan secara default saat menggunakan secara bashinteraktif, tetapi dinonaktifkan secara default saat menjalankan skrip. The shopt -s extglobperintah di atas script menyala itu.
Penjelasan: Extended Globbing
*( )@([1-9]*([0-9])|+(0))*( ), yang memeriksa input numerik , cocok dengan urutan:
- Nol atau lebih banyak spasi (
*( )). The *( )pertandingan konstruk nol atau lebih pola dalam kurung, yang di sini hanya spasi.
Sebenarnya ada dua jenis spasi putih horizontal, spasi, dan tab, dan sering diinginkan juga untuk mencocokkan tab. Tapi saya tidak khawatir tentang itu di sini, karena skrip ini ditulis untuk input manual, interaktif, dan -ebendera untuk readmemungkinkan GNU readline. Ini agar pengguna dapat bergerak bolak-balik dalam teks mereka dengan tombol panah kiri dan kanan, tetapi memiliki efek samping yang umumnya mencegah tab masuk secara harfiah.
- Satu kemunculan (
@( )) dari salah satu ( |):
- Digit bukan nol (
[1-9]) diikuti oleh nol atau lebih ( *( )) dari setiap digit ( [0-9]).
- Satu atau lebih (
+( )) dari 0.
- Nol atau lebih banyak spasi (
*( )), lagi.
*( )[qQ]?([uU][iI][tT])*( ), yang memeriksa perintah keluar , cocok dengan urutan:
- Nol atau lebih banyak spasi (
*( )).
qatau Q( [qQ]).
- Opsional - yaitu, nol atau satu kejadian (
?( )) - dari:
uatau U( [uU]) diikuti oleh iatau I( [iI]) diikuti oleh tatau T( [tT]).
- Nol atau lebih banyak spasi (
*( )), lagi.
Varian: Memvalidasi Input dengan Ekspresi Reguler Diperpanjang
Jika Anda lebih suka menguji input pengguna terhadap ekspresi reguler daripada glob shell, Anda mungkin lebih suka menggunakan versi ini, yang berfungsi sama tetapi menggunakan [[dan =~(seperti dalam jawaban terdon ) alih-alih casedan globbing diperpanjang.
#!/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
Kemungkinan keuntungan dari pendekatan ini adalah:
Dalam kasus khusus ini, sintaks sedikit lebih sederhana, setidaknya dalam pola kedua, di mana saya memeriksa perintah berhenti. Ini karena saya dapat mengatur nocasematchopsi shell, dan kemudian semua varian case qdan quitdibahas secara otomatis.
Itulah yang dilakukan shopt -s nocasematchperintah. The shopt -s extglobperintah dihilangkan sebagai globbing tidak digunakan dalam versi ini.
Keterampilan berekspresi reguler lebih umum daripada kemahiran dalam extglobs bash.
Penjelasan: Ekspresi Reguler
Adapun pola yang ditentukan di sebelah kanan =~operator, inilah cara kerja ekspresi reguler itu.
^\ *([1-9][0-9]*|0+)\ *$, yang memeriksa input numerik , cocok dengan urutan:
- Awal - yaitu, tepi kiri - garis (
^).
- Nol atau lebih (
*, diterapkan postfix) spasi. Sebuah ruang biasanya tidak perlu \-disingkirkan dalam ekspresi reguler, tetapi ini diperlukan [[untuk mencegah kesalahan sintaksis.
- A substring (
( )) yang merupakan salah satu atau yang lain ( |) dari:
[1-9][0-9]*: digit bukan nol ( [1-9]) diikuti oleh nol atau lebih ( *, diterapkan pada postfix) setiap digit ( [0-9]).
0+: satu atau lebih ( +, postfix yang diterapkan) dari 0.
- Nol atau lebih banyak spasi (
\ *), seperti sebelumnya.
- Akhir - yaitu, tepi kanan - dari garis (
$).
Tidak seperti caselabel, yang cocok dengan seluruh ekspresi yang diuji, =~mengembalikan true jika ada bagian dari ekspresi kiri yang cocok dengan pola yang diberikan sebagai ekspresi kanannya. Inilah sebabnya mengapa ^dan $jangkar, yang menentukan awal dan akhir baris, diperlukan di sini, dan tidak sesuai secara sintaksis dengan apa pun yang muncul dalam metode dengan casedan extglobs.
Tanda kurung diperlukan untuk membuat ^dan $mengikat pada disjungsi dari [1-9][0-9]*dan 0+. Kalau tidak, itu akan menjadi disjungsi dari ^[1-9][0-9]*dan 0+$, dan mencocokkan input apa pun yang dimulai dengan angka nol atau diakhiri dengan 0(atau keduanya, yang mungkin masih termasuk bukan digit di antaranya).
^\ *q(uit)?\ *$, yang memeriksa perintah keluar , cocok dengan urutan:
- Awal baris (
^).
- Nol atau lebih banyak ruang (
\ *, lihat penjelasan di atas).
- Surat itu
q. Atau Q, sejak shopt nocasematchdiaktifkan.
- Opsional - yaitu, nol atau satu kejadian (postfix
?) - dari substring ( ( )):
u, diikuti oleh i, diikuti oleh t. Atau, karena shopt nocasematchdiaktifkan umungkin U; secara mandiri, imungkin I; dan secara mandiri, tmungkin T. (Yaitu, kemungkinan tidak terbatas pada uitdan UIT.)
- Nol atau lebih banyak ruang lagi (
\ *).
- Akhir baris (
$).