Brevidade x legibilidade: um meio termo
Como você viu, esse problema admite soluções moderadamente longas e um tanto repetitivas, mas altamente legíveis ( respostas bash de Terdon e AB ), bem como aquelas que são muito curtas, mas não intuitivas e muito menos auto-documentadas ( python de Tim respostas bash e resposta perl de glenn jackman ). Todas essas abordagens são valiosas.
Você também pode resolver esse problema com o código no meio do continuum entre compacidade e legibilidade. Essa abordagem é quase tão legível quanto as soluções mais longas, com um comprimento mais próximo das soluções pequenas e esotéricas.
#!/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."
Nesta solução do bash, incluí algumas linhas em branco para melhorar a legibilidade, mas você pode removê-las se desejar ainda mais.
Linhas em branco incluídas, na verdade, são apenas um pouco mais curtas do que uma variante compactada, ainda bastante legível da solução bash da AB . Suas principais vantagens sobre esse método são:
- É mais intuitivo.
- É mais fácil alterar os limites entre as notas (ou adicionar notas adicionais).
- Ele aceita automaticamente entradas com espaços à esquerda e à direita (veja abaixo uma explicação de como
((
))
funciona).
Todas essas três vantagens surgem porque esse método usa a entrada do usuário como dados numéricos, em vez de examinar manualmente seus dígitos constituintes.
Como funciona
- Leia a entrada do usuário. Deixe que eles usem as teclas de seta para se mover no texto digitado (
-e
) e não interpretem \
como um caractere de escape ( -r
).
Esse script não é uma solução rica em recursos - veja abaixo um refinamento - mas esses recursos úteis apenas aumentam dois caracteres. Eu recomendo sempre usar -r
com read
, a menos que você saiba que precisa deixar o fornecimento do usuário \
escapar.
- Se o usuário escreveu
q
ou Q
, saia.
- Crie uma matriz associativa ( ). Preencha-o com a nota numérica mais alta associada a cada nota de letra.
declare -A
- Passe pelas notas das letras da mais baixa para a mais alta, verificando se o número fornecido pelo usuário é baixo o suficiente para cair no intervalo numérico de cada letra.
Com a ((
))
avaliação aritmética, os nomes de variáveis não precisam ser expandidos $
. (Na maioria das outras situações, se você quiser usar o valor de uma variável no lugar de seu nome, faça isso .)
- Se estiver dentro do intervalo, imprima a nota e saia .
Por uma questão de brevidade, uso o curto-circuito e o operador ( &&
) em vez de um if
- then
.
- Se o loop terminar e nenhum intervalo for correspondido, suponha que o número digitado seja muito alto (acima de 100) e informe ao usuário que ele estava fora do intervalo.
Como isso se comporta, com entrada estranha
Como as outras soluções curtas postadas, esse script não verifica a entrada antes de assumir que é um número. Avaliação aritmética ( ((
))
) tiras automaticamente líder e espaços em branco, de modo que é nenhum problema, mas:
- A entrada que não se parece com um número é interpretada como 0.
- Com entrada que se parece com um número (ou seja, se começa com um dígito), mas contém caracteres inválidos, o script emite erros.
- Entrada de dígitos múltiplos começando com
0
é interpretada como sendo , em octal . Por exemplo, o script dirá que 77 é um C, enquanto 077 é um D. Embora alguns usuários possam querer isso, provavelmente não querem e isso pode causar confusão.
- No lado positivo, quando recebe uma expressão aritmética, esse script a simplifica automaticamente e determina a nota da carta associada. Por exemplo, ele dirá que 320/4 é um B.
Uma versão expandida e com todos os recursos
Por esses motivos, convém usar algo como esse script expandido, que verifica se a entrada é boa e inclui alguns outros aprimoramentos.
#!/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
Essa ainda é uma solução bastante compacta.
Quais recursos isso adiciona?
Os pontos principais desse script expandido são:
- Validação de entrada. O script de terdon verifica a entrada com , por isso mostro outra maneira, que sacrifica alguma brevidade, mas é mais robusta, permitindo ao usuário entrar em espaços iniciais e finais e se recusando a permitir uma expressão que possa ou não ser pretendida como octal (a menos que seja zero) .
if [[ ! $response =~ ^[0-9]*$ ]] ...
- Eu usei
case
com globbing estendido em vez de [[
com o operador de =~
correspondência de expressão regular (como na resposta de terdon ). Fiz isso para mostrar que (e como) isso também pode ser feito dessa maneira. Globs e regexps são duas maneiras de especificar padrões que correspondem ao texto e qualquer um dos métodos é adequado para este aplicativo.
- Como o script bash da AB , incluí a coisa toda em um loop externo (exceto a criação inicial da
cutoffs
matriz). Ele solicita números e fornece notas correspondentes, desde que a entrada do terminal esteja disponível e o usuário não tenha solicitado que saia. A julgar pelo do
... done
ao redor do código da sua pergunta, parece que você deseja isso.
- Para facilitar a desistência, aceito qualquer variante que não diferencia maiúsculas de minúsculas de
q
ou quit
.
Esse script usa algumas construções que podem não ser familiares para iniciantes; eles estão detalhados abaixo.
Explicação: Uso de continue
Quando eu quero pular o resto do corpo do while
loop externo , eu uso o continue
comando Isso o leva de volta ao topo do loop, para ler mais entradas e executar outra iteração.
A primeira vez que faço isso, o único loop em que estou é o while
loop externo , para que eu possa ligar continue
sem nenhum argumento. (Estou em uma case
construção, mas isso não afeta a operação de break
ou continue
.)
*) echo "I don't understand that number."; continue;;
Na segunda vez, no entanto, estou em um for
loop interno que está aninhado dentro do while
loop externo . Se eu usasse continue
sem argumento, isso seria equivalente continue 1
e continuaria o for
loop interno em vez do while
loop externo .
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
Portanto, nesse caso, eu uso o continue 2
bash find e continuo o segundo loop.
Explicação: case
Etiquetas com Globs
Não uso case
para descobrir qual classe da letra bin um número cai (como em resposta a festança da AB ). Mas eu uso case
para decidir se a entrada do usuário deve ser considerada:
- um número válido,
*( )@([1-9]*([0-9])|+(0))*( )
- o comando quit,
*( )[qQ]?([uU][iI][tT])*( )
- qualquer outra coisa (e, portanto, entrada inválida),
*
Estes são globs de concha .
- Cada um é seguido por um
)
que não corresponde a nenhuma abertura (
, que é case
a sintaxe para separar um padrão dos comandos que são executados quando é correspondido.
;;
é case
a sintaxe para indicar o final dos comandos a serem executados para uma correspondência de caso paticular (e que nenhum caso subsequente deve ser testado após a execução).
O globbing de shell comum fornece *
para corresponder a zero ou mais caracteres, ?
para corresponder exatamente a um caractere e a classes / intervalos de caracteres entre [
]
colchetes. Mas estou usando globbing estendido , o que vai além disso. O globbing estendido é ativado por padrão ao usar bash
interativamente, mas é desativado por padrão ao executar um script. O shopt -s extglob
comando na parte superior do script o ativa.
Explicação: Globbing estendido
*( )@([1-9]*([0-9])|+(0))*( )
, que verifica a entrada numérica , corresponde a uma sequência de:
- Zero ou mais espaços (
*( )
). A *(
)
construção corresponde a zero ou mais do padrão entre parênteses, o que aqui é apenas um espaço.
Na verdade, existem dois tipos de espaço em branco horizontal, espaços e guias, e geralmente é desejável combinar as guias também. Mas não estou me preocupando com isso aqui, porque esse script foi escrito para entrada manual, interativa e o -e
sinalizador para read
ativar a linha de leitura do GNU. Isso é para que o usuário possa ir e voltar no texto com as teclas de seta esquerda e direita, mas tem o efeito colateral de impedir que as guias sejam inseridas literalmente.
- Uma ocorrência (
@(
)
) de qualquer um ( |
):
- Um dígito diferente de zero (
[1-9]
) seguido por zero ou mais ( *(
)
) de qualquer dígito ( [0-9]
).
- Um ou mais (
+(
)
) de 0
.
- Zero ou mais espaços (
*( )
), novamente.
*( )[qQ]?([uU][iI][tT])*( )
, que verifica o comando quit , corresponde a uma sequência de:
- Zero ou mais espaços (
*( )
).
q
ou Q
( [qQ]
).
- Opcionalmente - ou seja, zero ou uma ocorrência (
?(
)
) - de:
u
ou U
( [uU]
) seguido por i
ou I
( [iI]
) seguido por t
ou T
( [tT]
).
- Zero ou mais espaços (
*( )
), novamente.
Variante: validando entrada com uma expressão regular estendida
Se você preferir testar a entrada do usuário em relação a uma expressão regular em vez de um shell glob, você pode preferir usar esta versão, que funciona da mesma forma, mas usa [[
e =~
(como em resposta de terdon ) em vez de case
um globbing estendido.
#!/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
As possíveis vantagens dessa abordagem são as seguintes:
Nesse caso em particular, a sintaxe é um pouco mais simples, pelo menos no segundo padrão, em que verifico o comando quit. Isso porque pude definir onocasematch
opção de shell e, em seguida, todas as variantes de casos q
e quit
foram cobertas automaticamente.
É isso que o shopt -s nocasematch
comando faz. O shopt -s extglob
comando é omitido porque o globbing não é usado nesta versão.
Habilidades de expressão regular são mais comuns que a proficiência em extglobs do bash.
Explicação: Expressões regulares
Quanto aos padrões especificados à direita do =~
operador, veja como essas expressões regulares funcionam.
^\ *([1-9][0-9]*|0+)\ *$
, que verifica a entrada numérica , corresponde a uma sequência de:
- O início - ou seja, borda esquerda - da linha (
^
).
- Zero ou mais
*
espaços ( , postfix aplicado). Normalmente, um espaço não precisa ser \
escapado em uma expressão regular, mas isso é necessário com[[
para evitar um erro de sintaxe.
- Uma substring (
(
)
) que é uma ou outra (|
) de:
[1-9][0-9]*
: um dígito diferente de zero ( [1-9]
) seguido por zero ou mais ( *
, postfix aplicado) de qualquer dígito ([0-9]
).
0+
: um ou mais ( +
, postfix aplicado) de 0
.
- Zero ou mais espaços (
\ *
), como antes.
- O final - ou seja, a borda direita - da linha (
$
).
Ao contrário dos case
rótulos, que correspondem à expressão inteira sendo testada, =~
retornará true se alguma parte de sua expressão à esquerda corresponder ao padrão fornecido como sua expressão à direita. É por isso que as âncoras ^
e $
, especificando o início e o fim da linha, são necessárias aqui e não correspondem sintaticamente a nada que apareça no método com case
e extglobs.
Os parênteses são necessários para criar ^
e $
vincular à disjunção de [1-9][0-9]*
e 0+
. Caso contrário, seria a disjunção de ^[1-9][0-9]*
e 0+$
, e combinar qualquer entrada que começa com um dígito diferente de zero ou terminando com um0
(ou ambos, que ainda pode incluir não-dígitos no meio).
^\ *q(uit)?\ *$
, que verifica o comando quit , corresponde a uma sequência de:
- O início da linha (
^
).
- Zero ou mais espaços (
\ *
veja a explicação acima).
- A carta
q
. Ou Q
, desdeshopt nocasematch
está ativado.
- Opcionalmente - ou seja, zero ou uma ocorrência (postfix
?
) - da substring ((
)
):
u
, seguido por i
, seguido por t
. Ou, uma vez que shopt nocasematch
está ativado, u
pode ser U
; independentemente, i
pode ser I
; e independentemente, t
pode ser T
. (Ou seja, as possibilidades não se limitam a uit
e UIT
.)
- Zero ou mais espaços novamente (
\ *
).
- O fim da linha (
$
).
Você já tem a ideia básica. Se você deseja codificar isso
bash
(que é uma escolha razoável, já que é o shell padrão no Ubuntu e na maioria dos outros Linux), você não pode usá-case
lo porque ele não entende os intervalos. Em vez disso, você pode usarif
/else
:fonte
-ge
testes podem ser eliminados, presumivelmente, desde que você esteja usandoelif
. E sem amor(( $response < X ))
?(( $response < X ))
, com certeza, mas acho isso mais claro e o OP é obviamente novo no bash script.e uma versão mais compacta (Thx @EliahKagan ):
fonte
[0-59]
significa qualquer caractere de 0,1,2,3,4,5 ou 9 e assim por diante. Não vejo como isso pode funcionar para valores numéricos .Todas as instalações do Ubuntu têm Python, então aqui está um
scriptpython para um liner. Se você precisar que ele esteja no bash, também escrevi o equivalente como um script de shell .Para executar, salve-o em um arquivo (por exemplo
grade.py
) e execute-o no terminal com este:Isso é o que você vai ver:
Como é que isso funciona?
65
.065
.06
.70
.E
.E
.Meus pronomes são He / Him
fonte
input()
, ele irá chamareval()
, usoraw_input()
instead..also sua classificação não é certo como90+
irá imprimir grauB
..usechr(74 - max(4, num))
....input()
queraw_input()
para python2..thats-lo ..print chr(75-max(5,int('0'+raw_input('Enter the number: ')[:-1])))
python3
não temraw_input()
.. i sugeriuraw_input()
para seu inicial como você disse para executá-lo usandopython2
..Aqui está minha solução bash semi- esotérica, que preenche uma matriz com 101 entradas e depois verifica a entrada do usuário. Mesmo para uso no mundo real, isso é razoável - se você precisasse de excelente desempenho, não usaria o bash, e uma centena de tarefas ainda é rápida. Mas deixaria de ser razoável se estendido a uma faixa muito maior (como um milhão).
Vantagens:
q
,quit
ou qualquer coisa que começa comq
/Q
.Desvantagens:
g
é um array indexado unidimensional ). Como diz o velho ditado, "Não é um bug, é um recurso!" Bem, talvez.Legal, não é? (Bem, eu acho que sim.)
Como funciona
p
função p opula uma matriz numerada indexadag
de g rades, em índices que variam do primeiro argumento ao segundo, com o valor (letra) fornecido no terceiro argumento.p
é chamado para cada nota de letra, para definir seu intervalo numérico.q
(ouQ
), verifique ag
matriz para a qual a nota da letra corresponde ao número digitado e imprima essa letra.fonte
[[ $n =~ ^(0|[1-9]+[0-9]*)$ ]]
Depois de fazê-lo em Python 2 , decidi fazê-lo no bash.
Para executar, salve-o em um arquivo (por exemplo, grade.sh), torne-o executável
chmod +x grade.sh
e, em seguida, execute-o./grade.sh
.Isso é o que você vai ver:
Como é que isso funciona?
65
.065
(e o10#
mantém na base 10).06
.70
.E
.E
.Meus pronomes são He / Him
fonte
E aqui está a minha versão awk:
ou como uma linha:
fonte
Aqui está outra resposta "esotérica"
Explicação
perl -E
: the-E
, like-e
, permite passar um script como argumento de linha de comando. Esta é uma maneira de executar perl one-liners. Ao contrário-e
,-E
também habilita todos os recursos opcionais (comosay
, basicamente, umprint
com uma nova linha à direita).print "number: ";
: solicita que o usuário insira um número.$n = <>;
: salve esse número como$n
.A próxima parte precisa ser dividida um pouco.
qw/string/
avalia para uma lista feita quebrandostring
no espaço em branco. Então,qw/A A B C D E F F F F F/
é realmente esta lista:Portanto,
say qw/A A B C D E F F F F F/[11-($n+1)/10]
é equivalente aAgora, o Perl permite o uso de índices negativos para recuperar elementos contados no final da matriz. Por exemplo,
$arrray[-1]
imprimirá o último elemento da matriz. Além disso, os índices da matriz de ponto flutuante (por exemplo, 10.7) são automaticamente truncados para o próximo número inteiro mais baixo (10.7 ou 10.3 ou o que todos se tornarem 10.)O resultado de tudo isso é que o índice
11-($n+1)/10
sempre avalia o elemento (nota) apropriado da matriz.fonte
Embora você tenha solicitado uma solução bash, acho que em python, isso pode ser feito de maneira elegante e curta. Abrangendo os erros de manipulação em caso de entrada incorreta e a "conversão" de um número entre 0 e 100 em letras de A a F (ou qualquer outra):
Explicação
Primeiro, precisamos obter o número do usuário:
Testamos esse número para ser válido para várias condições:
Para cada um desses testes, o resultado será
False
ouTrue
. Portanto (compactando um pouco o código):produzirá uma figura de
0
para5
Posteriormente, podemos usar esta figura como um índice para uma string, para produzir um caractere como saída, por exemplo
produzirá "D" (desde o primeiro caractere = "A")
O adicional
101
à lista é gerar um erro (Índice-) caso o número exceda100
, pois"ABCDEF"[6]
não existe. O mesmo vale paran = n if n>=0 else ""
, que criará um erro (Valor-) se um número abaixo de 0 for inseridoNesses casos, bem como se a entrada não for uma figura, o resultado será:
Os testes:
fonte