F011 Más de Funciones y Arrays

Planteando un programa un poco más complicado
Ya sabemos definir funciones. Vamos a plantear un programa que acepte un número desde la consola y compruebe si es o no primo. Y en caso de no serlo, nos calcule cuales son los divisores primos de este.

Para resolver un problema complejo lo mejor es partirlo en otros problemas más pequeños que podamos resolver más fácilmente. 

En este caso vamos a plantear al menos 3 funciones:
Primo() – Calcula si un número dado es primo, devuelve true y en caso contrario false.
Divisores() – Para un número dado nos devuelve sus divisores primos.
GetLine() – Vamos a definir una función genérica que recoja una cadena de texto de la puerta serie, para procesarla a posteriori. En este caso recogerá el número a probar.

La idea es, que nuestro programa empiece comprobando si un número es primo. Si lo es, bien por él. Si no llamaremos a una función que calcule cuáles son sus divisores. Y por último necesitamos de algo que nos permita recibir en número de entrada. 

Con la función Primo() que vimos, a medida que el tamaño del número a probar crece, el tiempo que tarda en determinar si es primo también, ya que dividimos por todos los números que le preceden.

Una manera más eficaz de calcular si un número es primo, es dividirlo solo por los números primos menores que él. Pero para esto necesitaríamos un modo de archivar estos primos.

Podríamos ejecutar primero un programa para hallar los N primeros números primos, y si dispusiéramos de algún medio para guardarlos, tendríamos un sistema más eficaz para decidir si un número es o no primo.  Una manera de archivar estos números es definir un array.

Un array es simplemente una colección de elementos organizados como una matriz, y pueden definirse con varias dimensiones. Empecemos con un array de una sola dimensión. Para definirlo podemos optar por dos maneras:

int serie1 [5] ; //Creamos una colección de 5 enteros

int serie2[] = { 3,5,6,12, 23} ;   //Creamos una colección y definimos la valores de los 5 enteros

En el primer caso definimos un array de enteros, de una sola dimensión con 5 elementos, sin asignar valores de momento.

En el segundo caso asignamos un array de enteros a los valores que le pasamos entre llaves, sin especificar cuantos, porque le dejamos a C++ la tarea de contarlos. Decimos que definimos el array por enumeración.

Para asignar o leer los valores de un array se utiliza un índice entre llaves egipcias. 

int serie2[ ] = { 3,5,6,12, 23} ;            
void setup(){
  Serial.begin(115200) ;  
}

void loop(){
  for (int i=0 ; i<5 ; i++)
    Serial.println("Posicion " + String(i)+ ": "+ String(serie2[i])) ;
  delay(3000);
}


El programa imprime el contenido del array recorriendo sus 5 posiciones.

Atención: la primera posición de un array es la 0 y la última el número de elementos – 1. Así serie2 [0] devuelve el primer elemento 3, y serie2[4] el último 23. 
 
Un error muy peligroso, y difícil de detectar sería algo así 
void loop(){
  int serie2[] = { 3,5,6,12, 23} ;
    for (int i=0 ; i<99 ; i++)
         Serial.println("Posicion " + String(i)+ ": "+ String(serie2[i])) ;
  delay(3000);
}

Uno esperaría que C++ generase un error, ya que definimos un array de 5 elementos y hacemos referencia a 100, pero no. Nuevamente C++ nos sorprende devolviendo correctamente los 5 primeros valores y luego sigue leyendo posiciones de memoria consecutivas tan tranquilo, como si tuvieran sentido.  C++ espera que seamos nosotros quienes controlemos esto, así que mucho cuidado. Por último, mencionar que podemos manejar arrays de varias dimensiones:

int Tablero[8][8];

Imaginad que Tablero representa las posiciones de una partida de ajedrez y cada valor que contiene esa posición corresponde a una pieza que se encuentra en esa casilla.
 
Afinando la función Primo()
Si ejecutamos el programa nos dará en el monitor una lista de primos hasta el 1024 (o hasta el número que deseemos modificando el valor de máximo) y seleccionándolos con el ratón podremos copiar esos valores y pegarlos en el IDE para crear un array con ellos 
int P[] =
{          2,            3,             5,          7,         11,        13,          17,         19,
          23,           29,            31,         37,         41,        43,          47,         53,
          59,           61,            67,         71,         73,        79,          83,         89,
          97,          101,           103,        107,        109,        113,        127,        131,
         137,          139,           149,        151,        157,        163,        167,        173,
         179,          181,           191,        193,        197,        199,        211,        223,
         227,          229,           233,        239,        241,        251,        257,        263,
         269,          271,           277,        281,        283,        293,        307,        311,
         313,          317,           331,        337,        347,        349,        353,        359,
         367,          373,           379,        383,        389,        397,        401,        409,
         419,          421,           431,        433,        439,        443,        449,        457,
         461,          463,           467,        479,        487,        491,        499,        503,
         509,          521,           523,        541,        547,        557,        563,        569,
         571,          577,           587,        593,        599,        601,        607,        613,
         617,          619,           631,        641,        643,        647,        653,        659,
         661,          673,           677,        683,        691,        701,        709,        719,
         727,          733,           739,        743,        751,        757,        761,        769,
         773,          787,           797,        809,        811,        821,        823,        827,
         829,          839,           853,        857,        859,        863,        877,        881,
         883,          887,           907,        911,        919,        929,        937,        941,
         947,          953,           967,        971,        977,        983,        991,        997,
         1009,        1013,          1019,        1021 }  ;

Hemos definido un array enumerando sus elementos, entre llaves y separados por comas.  Es importante percatarse de que después de copiar y pegar las salida hemos borrado la coma después del 1021, porque si no daría un error de sintaxis al definir el array.    

Obsérvese que hay un punto y coma después de la llave de cierre del array. Aunque está entre llaves es una instrucción de asignación y no una definición de función.  

Al definir el array por enumeración, si el número es alto podemos perder de vista cuantos elementos contiene. Si necesitamos calcular el número de miembros podemos utilizar la función sizeof():

 int size = sizeof(P) / sizeof(int);

Donde P es nuestro array y dividimos por sizeof(int) porque definimos P como int. Y para este caso devuelve un valor de 172 elementos.

Ahora bastaría dividir el número a probar por aquellos elementos del array P, menores que él:

bool Primo(int x){
  int index = 0 ;
  while ( P[index]  <  x){
    if ( x  %  P[index++] == 0)
      return(false);
  }
  return(true);
}

Recorremos los valores almacenados en el array P[ ] mediante index al que vamos incrementando en cada iteración, hasta que nos toque probar un primo mayor que el valor que comprobamos.
 
Función Divisores
Esta función va a recorrer los elementos del array en tanto en cuanto sean menores (posibles divisores) que el número que probamos. 

Si encontramos divisores primos los guardamos en un array que hemos llamado Div[ ] al que le asignamos un máximo de 100 divisores:

int Divisores(int n) {
    int contador = 0;
    for (int i = 1; i <= n; i++) {
        if (n % i == 0) {
            divisores[contador] = i;
            contador++;
        }
    }
    return contador;
}

Cuando queramos imprimir los divisores, basta con recorrer Div[].

Es importante entender que tanto la función Primo() como Divisores() recorren el array de números primos hasta que sobrepasan el valor del número a probar. Si el número que probamos es mayor que el máximo primo que contiene P[ ], podemos obtener resultados extraños, ya que leeremos más elementos de los que hemos definido.  Este método de buscar divisores es válido solo para números inferiores a 1024 ( o en su caso, al máximo número hasta el que hayamos calculado primos), porque un número no será primo si Primo() lo afirma, ya que encontrará divisores. Pero puede afirmar que un número es primo erróneamente si sus divisores son superiores al máximo primo en P[ ]. 

La función GetLine()
Aunque ya comentamos que podemos usar una función parseInt () incluida en Arduino para recoger un valor del puerto serie, tiene el inconveniente de que si no recibe una entrada salta al cabo de un tiempo y devuelve 0, por lo que tendríamos que controlar el valor devuelto para que no se repitiese continuamente. 

Por eso vamos a escribir una función de uso general que nos permita recoger una cadena de texto de la puerta serie sin que salga hasta que reciba un String que vamos a hacer finalice en intro.
 
String GetLine() {
    String s = "";
    while (true) {
        if (Serial.available()) {
            char c = Serial.read();
            if (c == '\n')
                break;
            if (c != '\r')
                s += c;
        }
    }
    return s;
}

Definimos Getline() de tipo String, porque queremos que nos devuelva un texto. Comprobamos si hay algo disponible en la puerta serie, y en caso afirmativo construimos un String S añadiéndole cada uno de los caracteres que leemos del puerto serie, hasta que encontremos un intro. Al encontrar el intro, se cumple la condición de salida del while y termina la función devolviendo la cadena construida (sin el intro).  

Normalmente convendrá comprobar si hay algo disponible en la puerta serie antes de llamar a GetLine(), y si es así, la comprobación que hace GetLine() de tener algo disponible en el Serial sería redundante.  Pero si llamáramos a GetLine() sin comprobarlo y esta no lo controlase, quedaríamos atrapados en esta función hasta que alguien escribiera algo finalizado con intro para poder salir y podría no ser sencillo comprender el problema.

Nuevamente hemos incluido un delay de 25 ms en el while para asegurarnos de que Arduino no puede volver a leer más caracteres antes de que a la velocidad de 9600 bps haya llegado el próximo carácter. Si la velocidad de comunicación es de 115200 bits por segundo o más, se puede suprimir este retraso. 
 
El programa principal
Podemos ya escribir nuestra función principal loop(), que llame a las funciones que hemos definido a lo largo de esta sesión, para determinar si un número que le pasamos por la puerta serie es primo o no y en caso negativo que nos muestre los divisores primos encontrados.
Podría ser algo así:

void loop() {
    if (Serial.available()) {
        String s = GetLine();
        int i = s.toInt();
        if (Primo(i)) {
            Serial.println(String(i) + " Es primo.");
        }
        else {
            Serial.println(String(i) + " No es primo.");
            Serial.println("Sus divisores son:");
            int j = Divisores(i);
            for (int n = 0; n < j; n++) {
                Serial.print(divisores[n]);
                Serial.print(", ");
            }
            Serial.println();
        }
    }
}

Empezamos comprobando si hay algo sin leer en la puerta serie y si es así llamamos a GetLine() para que nos consiga lo que hay.  

Como GetLine() nos devuelve un tipo String() usamos la función estándar de Arduino C++, s.toInt() que convierte el contenido String a tipo numérico int. 

Después llamamos a Primo() para que compruebe este número. Si es primo, simplemente imprime un mensaje para confirmarlo. En caso contrario llamamos a Divisores() que busca y almacena en el array Div[] los divisores primos que encuentra. 

Cuando divisores regresa, nos devuelve el número de divisores encontrados y podemos imprimirlos con un sencillo bucle for.

Este es el código completo


int P[] =
{          2,            3,             5,          7,         11,        13,          17,         19,
          23,           29,            31,         37,         41,        43,          47,         53,
          59,           61,            67,         71,         73,        79,          83,         89,
          97,          101,           103,        107,        109,        113,        127,        131,
         137,          139,           149,        151,        157,        163,        167,        173,
         179,          181,           191,        193,        197,        199,        211,        223,
         227,          229,           233,        239,        241,        251,        257,        263,
         269,          271,           277,        281,        283,        293,        307,        311,
         313,          317,           331,        337,        347,        349,        353,        359,
         367,          373,           379,        383,        389,        397,        401,        409,
         419,          421,           431,        433,        439,        443,        449,        457,
         461,          463,           467,        479,        487,        491,        499,        503,
         509,          521,           523,        541,        547,        557,        563,        569,
         571,          577,           587,        593,        599,        601,        607,        613,
         617,          619,           631,        641,        643,        647,        653,        659,
         661,          673,           677,        683,        691,        701,        709,        719,
         727,          733,           739,        743,        751,        757,        761,        769,
         773,          787,           797,        809,        811,        821,        823,        827,
         829,          839,           853,        857,        859,        863,        877,        881,
         883,          887,           907,        911,        919,        929,        937,        941,
         947,          953,           967,        971,        977,        983,        991,        997,
         1009,        1013,          1019,        1021 }  ;
// Array donde guardaremos los divisores
int divisores[100];
//----------------------------------------------------
// setup
//----------------------------------------------------
void setup() {
    Serial.begin(115200);
    Serial.println("Introduce un numero:");
}
//----------------------------------------------------
// loop
//----------------------------------------------------
void loop() {
    if (Serial.available()) {
        String s = GetLine();
        int i = s.toInt();
        if (Primo(i)) {
            Serial.println(String(i) + " Es primo.");
        }
        else {
            Serial.println(String(i) + " No es primo.");
            Serial.println("Sus divisores son:");
            int j = Divisores(i);
            for (int n = 0; n < j; n++) {
                Serial.print(divisores[n]);
                Serial.print(", ");
            }
            Serial.println();
        }
    }
}
//----------------------------------------------------
// Comprueba si un numero es primo
//----------------------------------------------------
bool Primo(int x){
  int index = 0 ;
  while ( P[index]  <  x){
    if ( x  %  P[index++] == 0)
      return(false);
  }
  return(true);
}
//----------------------------------------------------
// Guarda los divisores en el array divisores[]
// y devuelve cuantos hay
//----------------------------------------------------
int Divisores(int n) {
    int contador = 0;
    for (int i = 1; i <= n; i++) {
        if (n % i == 0) {
            divisores[contador] = i;
            contador++;
        }
    }
    return contador;
}
//----------------------------------------------------
// Lee una linea del monitor serie
//----------------------------------------------------
String GetLine() {
    String s = "";
    while (true) {
        if (Serial.available()) {
            char c = Serial.read();
            if (c == '\n')
                break;
            if (c != '\r')
                s += c;
        }
    }
    return s;
}
de la sesión

Hemos operado con arrays de una única dimensión tanto para leer su contenido como para modificarlo.

Hemos utilizado el programa de primos como excusa para mostrar cómo se pueden resolver problemas complejos, dividiéndolos en otros más sencillos que podamos resolver con funciones simples. Esta es, sin duda, una de las habilidades clave para ser un programador competente, y como todo en la vida requiere practica e ir trabajando.  

También hemos definido una función de propósito general GetLine() que utilizaremos más veces en el futuro.

No hay comentarios:

Publicar un comentario