Por que usar && 75 vezes mais rápido do que se… fi e como tornar o código mais claro

38

Eu tenho o seguinte código de trabalho:

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"

Esse código é executado rapidamente é de 0,194 segundo. No entanto, achei o && is_prime= false um pouco difícil de ler e poderia parecer (para o olho destreinado) como se estivesse sendo testado, em vez de ser definido, e é isso que ele faz. Então eu tentei mudar o && em um if...then e isso funciona - mas é 75 vezes mais lento em 14,48 segundos. É mais perceptível nos números mais altos.

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"

Existe alguma que tenha a clareza do bloco sem a lentidão?

Atualização (1/4/2015 10:40 am EST)

Ótimo feedback! Agora estou usando o seguinte. Algum outro feedback?

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"
    
por Michael Durrant 03.01.2015 / 16:37

2 respostas

66

Isso porque você está gerando um sub-shell toda vez:

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

Basta remover os parênteses

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

Se você deseja agrupar comandos, existe uma sintaxe para fazer isso no shell atual :

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

(o ponto-e-vírgula final é obrigatório, consulte o manual )

Observe que [ is_prime ] não é o mesmo que [ $is_prime == true ] : você poderia escrever isso como simplesmente $is_prime (sem colchetes), o que invocaria o comando bash true ou false incorporado. > [ is_prime ] é um teste com um argumento, a string "is_prime" - quando [ recebe um único argumento, o resultado é sucesso se o argumento não é vazio, e essa string literal é sempre não vazia, portanto, sempre "verdadeiro".

Por questões de legibilidade, eu mudaria a linha muito longa

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

para

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

Não subestime o espaço em branco para melhorar a clareza.

    
por 03.01.2015 / 16:54
9

Eu acho que você está trabalhando demais nessa função. Considere:

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

A aritmética da shell é bastante capaz de avaliar condições inteiras por conta própria. Ele raramente precisa de muitos testes e / ou atribuições externas. Esse loop while duplica muito bem seus loops aninhados:

Ele não imprime tanto, é claro, eu não escrevi tanto assim, mas, por exemplo, definindo o teto para 16 ao invés de 101 como está escrito acima e ...

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

Definitivamente está fazendo o trabalho. E requer muito pouco mais para aproximar sua saída:

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

Basta fazer isso em vez de usar echo e ...

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

Isso funciona em busybox . É muito portátil, rápido e fácil de usar.

Seu problema de subshell ocorrerá na maioria dos shells, mas é, por muito , mais agudo em um shell bash . Eu alternava entre fazer

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

... e a maneira como eu escrevi acima em vários shells para um teto de 101 e dash fizeram isso sem o subshell em 0.177 segundos e com o subshell em 1.8 segundos. busybox .149 e 2, zsh .149 e 4, bash .35 e 6 e ksh93 em .149 e .160. ksh93 não bifurca para subshells como as outras shells devem. Então talvez o problema não seja tanto o subshell quanto o shell .

    
por 04.01.2015 / 00:29