segunda-feira, 10 de janeiro de 2022

Encontrando números primos - parte III

Nas aulas anteriores, desenvolvemos um algoritmo capaz de encontrar números primos do número 2 a um número máximo fornecido. Também otimizamos nosso algoritmo, descartando operações desnecessárias. Se você não viu as aulas anteriores, recomendo fortemente que veja a Parte I e a Parte II.

Suponha que estamos interessados nos números primos de 2 a 100. Então, executamos nosso algoritmo passando como argumento o número 100 para a função calcula_primos2(). Mas, se quando obtermos o resultado, queiramos continuar encontrando os números primos até o valor 1000? Se executamos nosso algoritmo novamente, agora com o número 1000, ele vai calcular todos os primos de 2 a 100 novamente, o que é desnecessário, uma vez que já os encontramos. Então, como aproveitamos o primeiro resultado?

Vamos fazer o seguinte: após a finalização do algoritmo, perguntamos ao usuário se quer continuar o procedimento. Caso negativo, paramos o algoritmo e retornamos o resultado. Caso afirmativo, perguntamos o próximo valor máximo e continuamos a execução do algoritmo. Fazemos isso até que o usuário indique a finalização e retornamos o resultado.

Para isso, utilizaremos a função readline(). Essa função permite que o usuário entre com um valor (no caso, de texto) por linha de comando. Por exemplo, se queremos perguntar "Deseja continuar?", escrevemos

readline(prompt = "\nDeseja continuar? y/n ")
No caso, já informamos que a resposta deve ser yes (sim) ou no (não). Se executamos o código acima, obtemos


Digitando "y" na linha de comando, temos:


Observe que a função retorna a resposta do usuário. Logo, precisamos armazená-la em uma variável para verificar se a resposta é positiva ou negativa.

Vamos implementar uma função que pergunta ao usuário o número máximo para encontrar números primos, encontra os números primos até o valor fornecido e, ao final, pergunta se o usuário deseja continuar o procedimento. Vejamos:
encontra_primos = function(){
  para_alg  = F;
  num_ini   = 3;
  
  v_primos = NULL;
  v_primos[1] = 3;
  
  while(para_alg==F){
    var1 = readline(prompt = "\n Digite o valor do número máximo: ")
    num_max = as.numeric(var1);
    
    if(num_ini>num_max){
      cat("O valor máximo deve ser maior que ",num_ini,"\n");
    }else{
      v_num = seq(num_ini,num_max,by=2);
      num_max = v_num[length(v_num)];
      
      count = 1;
      while(count<=length(v_num)){
        testa_primo = T;
        for(i in 1:length(v_primos)){
          if(v_primos[i]>sqrt(v_num[count])){
            break;
          }
          if(v_num[count]%%v_primos[i]==0){
            testa_primo = F;
            break;
          }
          
        }
        if(testa_primo==T) v_primos = c(v_primos,v_num[count]);
        count = count + 1;
      }
      varc = readline(prompt = "\nDeseja continuar? y/n ")
      if(varc=='n'){ para_alg=T;
      }else{
        num_ini = num_max + 2;
      }
    }
  }
  v_primos[1] = 2;
  return(v_primos)
  
}
Vamos entender a função acima. Note que ela não precisa de argumentos, uma vez que o único argumento (o número máximo) é passado pelo usuário. 

Primeiro, definimos algumas variáveis. para_alg é uma variável booleana que determina se o algoritmo vai parar ou não e é iniciada com false. num_ini é o número inicial do nosso vetor de números ímpares.
  para_alg  = F;
  num_ini   = 3;
Enquanto não houver parada definida pelo usuário, o algoritmo será executado:
  while(para_alg==F){...}
Dentro do comando while, obtemos do usuário o valor máximo do nosso vetor de números ímpares para executar o algoritmo:
  var1 = readline(prompt = "\n Digite o valor do número máximo: ")
  num_max = as.numeric(var1);
No código acima, armazenamos o valor que o usuário digita na variável var1, que está como texto, transformamos o valor em número e armazenamos na variável num_max. Se o número informado pelo usuário for menor que o valor de num_ini, printamos uma mensagem e perguntamos novamente o valor desejado. Caso contrário, executamos o algoritmo:
    if(num_ini>num_max){
      cat("O valor máximo deve ser maior que ",num_ini,"\n");
    }else{
      [...]
    }
Assim que a execução entra no bloco else, criamos nosso vetor de números ímpares. É importante armazenar o maior número do nosso vetor, pois ele será referência para construir o próximo, caso o usuário deseje continuar o algoritmo.
    v_num = seq(num_ini,num_max,by=2);
    num_max = v_num[length(v_num)];
As demais linhas já foram discutidas. Elas definem nosso algoritmo de encontrar números primos. Assim que essa execução termina, perguntamos ao usuário se deseja continuar executando o algoritmo. Caso negativo ("n"), o algoritmo deve parar e para_alg é igual a true. Caso contrário, o novo número inicial é o último número do vetor de ímpares acrescido de 2, ou seja, o próximo número ímpar. Isso é feito nas seguintes linhas de código:
    varc = readline(prompt = "\nDeseja continuar? y/n ")
    if(varc=='n'){ para_alg=T;
    }else{
        num_ini = num_max + 2;
    }
Ao final, quando o usuário decide parar a execução do algoritmo, devemos retornar o resultado do vetor de números primos acrescido do número 2 (que não utilizamos no nosso método):
  v_primos[1] = 2;
  return(v_primos)
Note que, no código acima, em vez de concatenar o número 2 ao vetor v_primos, estamos alterando a primeira posição do vetor pelo número 2. Isso deve ser feito porque o vetor v_primos possui o número 3 na primeira e na segunda posição. Por quê? Quando mudamos a estratégia para parar a verificação quando o número do vetor é maior que a raiz quadrada do número testado, o número 3 (que já estava no vetor) é colocado novamente no vetor v_primos, pois 3 > 1,73. Teste na sua máquina e veja o resultado. Um exemplo de saída da função encontra_primos() é a seguinte:
> encontra_primos()

 Digite o valor do número máximo: 10

Deseja continuar? y/n y

 Digite o valor do número máximo: 100

Deseja continuar? y/n y

 Digite o valor do número máximo: 1000

Deseja continuar? y/n n
  [1]   2   3   5   7  11  13  17  19  23  29  31  37  41  43  47  53  59  61
 [19]  67  71  73  79  83  89  97 101 103 107 109 113 127 131 137 139 149 151
 [37] 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251
 [55] 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359
 [73] 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463
 [91] 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593
[109] 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701
[127] 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827
[145] 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953
[163] 967 971 977 983 991 997
Em questão de execução e retorno do resultado, nossa função está completa. Em relação a interação com o usuário, ela ainda apresenta alguns problemas que devem ser tratados:

1) Se o usuário digita algo que não seja número, o algoritmo dará erro, pois não tem como transformar em numeric um texto (e digo texto no sentido de digitar letras ou caracteres especiais).

2) O usuário pode digitar qualquer coisa além de "y" ou "n" na pergunta de continuar a execução do algoritmo. Ou seja, qualquer coisa que seja diferente de "n" significa que o usuário deseja continuar.

Esses tratamentos são simples e não serão abordados aqui. Deixo como exercício para você realizar.

Na próxima aula vou falar sobre um algoritmo bastante famoso para encontrar números primos dado um número máximo: o crivo de Eratóstenes. Vamos implementá-lo e compará-lo com o nosso algoritmo desenvolvido aqui.

Então, até a próxima aula!

Nenhum comentário:

Postar um comentário