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 -r
dengan read
, kecuali Anda tahu Anda harus membiarkan pasokan pengguna \
lolos.
- Jika pengguna menulis
q
atau 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
0
yang 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
case
dengan 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
cutoffs
array). Ia meminta angka dan memberikan nilai huruf yang sesuai selama input terminal tersedia dan pengguna belum menyuruhnya untuk berhenti. Dilihat oleh do
... di done
sekitar kode dalam pertanyaan Anda, sepertinya Anda menginginkannya.
- Agar mudah berhenti, saya menerima varian case-insensitive
q
atau 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 while
loop luar , saya menggunakan continue
perintah. 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 while
loop luar , jadi saya bisa menelepon continue
tanpa argumen. (Saya dalam case
konstruksi, tetapi itu tidak mempengaruhi operasi break
atau continue
.)
*) echo "I don't understand that number."; continue;;
Namun, kedua kalinya saya berada di for
loop dalam yang bersarang di dalam while
loop luar . Jika saya menggunakan continue
tanpa argumen, ini akan sama dengan continue 1
dan akan melanjutkan for
loop dalam, bukan while
loop luar .
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
Jadi dalam hal itu, saya gunakan continue 2
untuk membuat bash find dan melanjutkan loop kedua sebagai gantinya.
Penjelasan: case
Label dengan Glob
Saya tidak menggunakan case
untuk mencari tahu mana-huruf bin angka jatuh ke (seperti dalam AB jawaban pesta ). Tapi saya gunakan case
untuk 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 case
sintaks untuk memisahkan pola dari perintah yang dijalankan saat dicocokkan.
;;
adalah case
sintaks 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 bash
interaktif, tetapi dinonaktifkan secara default saat menjalankan skrip. The shopt -s extglob
perintah 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 -e
bendera untuk read
memungkinkan 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 (
*( )
).
q
atau Q
( [qQ]
).
- Opsional - yaitu, nol atau satu kejadian (
?(
)
) - dari:
u
atau U
( [uU]
) diikuti oleh i
atau I
( [iI]
) diikuti oleh t
atau 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 case
dan 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 nocasematch
opsi shell, dan kemudian semua varian case q
dan quit
dibahas secara otomatis.
Itulah yang dilakukan shopt -s nocasematch
perintah. The shopt -s extglob
perintah 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 case
label, 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 case
dan 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 nocasematch
diaktifkan.
- Opsional - yaitu, nol atau satu kejadian (postfix
?
) - dari substring ( (
)
):
u
, diikuti oleh i
, diikuti oleh t
. Atau, karena shopt nocasematch
diaktifkan u
mungkin U
; secara mandiri, i
mungkin I
; dan secara mandiri, t
mungkin T
. (Yaitu, kemungkinan tidak terbatas pada uit
dan UIT
.)
- Nol atau lebih banyak ruang lagi (
\ *
).
- Akhir baris (
$
).