|
Un palíndromo es una palabra que si se lee de izquierda a derecha suena igual que de derecha a izquierda, por ejemplo, ANILINA, RASAR, NADAN. El término palíndromo es a las palabras lo mismo que capicúa es a los números. Un ejercicio común para los principiantes es la elaboración de una función o método que compruebe si una cadena dada es un palíndromo.
No es un desafío demasiado complicado, pero presenta una serie de problemas que es necesario conocer.
El primero de ellos es el de la simetría. Muchas veces no caemos en ello, pero a la hora de comprobar si una palabra es un palíndromo, intentamos demostrar que existe una simetría en ella. Lo más sensato es comparar el primer carácter con el último y si son iguales, es que es posible que la palabra sea palíndromo. Entonces seguiremos... comparando el segundo con penúltimo, el tercero con el antepenúltimo... es decir, comparamos el carácter de la posición n con el de la posición longitud-n ¿Cuántas comparaciones debemos hacer como máximo? A menudo he oido respuestas como... "tantas como carácteres tenga la cadena". Rotundamente no.... basta con recorrer la mitad de la cadena. Al llegar a la mitad ya habremos comparado cada caracter de la primera mitad con cada carácter de la segunda. Es más... si la cadena tuviera un número impar de carácteres, por ejemplo 5, ni siquiera hace falta llegar a la mitad exacta de la cadena. En una cadena de longitud 5, su mitad exacta sería el 3, porque el tercer carácer estaria exactamente enmedio. Basta llegar al 2. Por ejemplo, en NADAN, basta comprobar la N con la N y la A con la A. La D nos da igual... podría haber cualquier carácter, y la palabra seguiría siendo un palíndromo.
El segundo problema es cuándo parar. Es un asunto trivial, pero hemos de caer en él. Muchos programadores recorrerían hasta la mitad de la cadena siempre, sin reparar en que en el momento en que encontremos un carácter que no es igual al colocado simétricamente ya sabemos que la cadena no puede ser un palíndromo, y debemos parar. Eso nos condiciona el tipo de bucle que escojamos... en esta ocasión no puede ser un bucle for, ya que éste está pensado para tener un recorrido que no se interrumpa, y nosotros vamos a interrumpir el recorrido en cuanto podamos.
Este código, por ejemplo, no es correcto. Entre otras cosas, porque si pasamos para comprobar la cadena "SUPERCALIFRAGILISTICOEXPIALIDOSO", el algoritmo comprobará hasta la mitad de la cadena para decidir que no es un palíndromo, cuando se sabría con sólo una comparación (la 'S' inicial con la 'O' final). Utilizamos una variable booleana, que inicializamos a true, suponiendo que la palabra es palíndromo. Con el bucle, intentamos encontrar una prueba de que no lo es. Si lo encontramos, cambiamos el valor de la variable... pero seguimos comprobando hasta la mitad de la palabra
//este código es INCORRECTO
static bool comprobarPalindromo(String a)
{
int mitad = a.Length / 2;
bool esPalindromo = true;
for (int cuenta = 0; cuenta <= mitad; cuenta++)
{
if (a[cuenta] != a[a.Length - cuenta - 1])
esPalindromo = false;
};
return esPalindromo;
}
Probemos de otra forma... Quizá utilizando un bucle while, o un do-while. Utilizaremos un bucle do-while si necesitamos iterar al menos una vez, y un bucle while si necesitamos iterar 0 o más veces. ¿Necesitamos al menos una comprobación para determinar que no es un palíndromo? Aparentemente, la respuesta es SÍ (Sólo aparentemente).
//este código es INCORRECTO
static bool comprobarPalindromo(String a)
{
int mitad = a.Length / 2;
int cuenta = 0;
bool esPalindromo = true;
do
{
if (a[cuenta] != a[a.Length - cuenta - 1])
esPalindromo = false;
cuenta++;
} while (esPalindromo && mitad>cuenta);
return esPalindromo;
}
En esta ocasión, utilizamos una variable para demostrar que la palabra no es palíndromo. Esa variable, servirá para salir del bucle, además de la comprobación de no sobrepasar la mitad.
El planteamiento es casi correcto. ¿qué pasa con la cadena vacía? La cadena vacía es un palíndromo, porque se lee igual de derecha a izquierda de de izquierda a derecha. Tal y como está el algoritmo, al pasar la cadena vacía, provoca un error de ejecución porque rebasamos los índices de la cadena. Además, ¿Qué pasa con las cadenas de longitud 1? Este algoritmo comprueba que la letra en la posición "1" sea igual a la letra en la posición "1"... no es necesario. Todas las cadenas de longitud 1 son palíndromos. Podríamos meter una comprobación... y si la longitud es 0 o 1, devolver true directamente.
//este código es INCORRECTO
static bool comprobarPalindromo(String a)
{
if (a.Length<2)
return true;
int mitad = a.Length / 2;
int cuenta = 0;
bool esPalindromo = true;
do
{
if (a[cuenta] != a[a.Length - cuenta - 1])
esPalindromo = false;
cuenta++;
} while (esPalindromo && mitad>cuenta);
return esPalindromo;
}
Estupendo... Hemos puesto dos puntos de salida a la función.
//este código es INCORRECTO
static bool comprobarPalindromo(String a)
{
bool esPalindromo = true;
if (a.Length>1)
{
int mitad = a.Length / 2;
int cuenta = 0;
do
{
if (a[cuenta] != a[a.Length - cuenta - 1])
esPalindromo = false;
cuenta++;
} while (esPalindromo && mitad>cuenta);
}
return esPalindromo;
}
Ahora está mejor... pero estamos tratando al 0 y al 1 como una excepción a la regla. Quizá podamos mejorarlo. Hemos supuesto que la iteración debe hacerse al menos una vez, cosa que no es necesariamente cierta. Quizá el caso de la longitud 0 y la longitud 1 pudieran formar parte de un bucle más general que se repitiera 0 o más veces... es decir un bucle while. Vamos a probar. Tomaremos el enfoque de ir comprobando la cadena con un bucle while, y parar cuando encontremos que el carácter situado en una posición y el situado en la posición simétrica difieren. Si hemos alcanzado la mitad de la cadena significa que ninguno difiere, y si hemos parado antes, sí que difieren, con lo que la cadena será palíndromo
//este código es INCORRECTO
static bool comprobarPalindromo(String a)
{
int mitad=a.Length/2;
int cuenta=0;
bool esPalindromo=false;
while ((a[cuenta] == a[a.Length - cuenta-1])
&& (cuenta<=mitad))
cuenta++;
if (cuenta > mitad)
esPalindromo = true;
return esPalindromo;
}
Nuevamente hemos tomado un enfoque incorrecto. Estamos queriendo que el bucle funcione para cadenas de longitud 0 y 1, y sin embargo, la condición del bucle hace que no funcione con longitud 0, porque intentamos acceder siempre al menos al primer carácter, y además, en las de longitud 1, se comprueba que la primera letra sea igual a la primera letra.
Demos una vuelta de tuerca más... Si la estrategia a seguir es suponer que la palabra a es un palíndromo, y si no lo es, en cuanto lo tengamos claro terminamos y lo decimos, utilicemos una variable booleana para señalizar nuestra intención... inicializaremos esa variable a true, en la suposición de que la palabra es palíndromo, e intentemos demostrar con nuestro bucle que no lo es. En cuanto lo logremos, pongamos esa variable a false, y esa misma variable nos debe hacer salir del bucle. Por supuesto, es posible que esa variable nunca cambie a false, con lo que también debemos salir del bucle al haber recorrido la mitad.
//este es más CORRECTO
static bool comprobarPalindromo(String a)
{
int mitad = a.Length / 2;
int cuenta = 0;
bool esPalindromo = true;
while (esPalindromo && mitad>cuenta)
{
if (a[cuenta] != a[a.Length - cuenta - 1])
esPalindromo = false;
cuenta++;
};
return esPalindromo;
}
Éste último es más correcto que los anteriores, aunque pudieramos argumentar que los anteriores también funcionan, aunque sea en determinadas circurnstancias. Por un lado, este algoritmo no considera ningún caso como una excepción. Con cadenas de longitud 0 y 1 no se entra en el bucle, y se devuelve el valor inicial de la variable esPalindromo, es decir, true. Con los de longitud superior, el bucle para en el momento en que se demuestra que la cadena no es palíndromo, y si no se demuestra, la condición mitad>cuenta hace que se salga del bucle también
Saber cuando un algoritmo es el más correcto que soluciona un problema no es un asunto trivial, pero darse cuenta de que en determinadas ocasiones se pueden hacer mejor las cosas no suele ser muy complicado.
El mismo esquema lo puedes utilizar en otros problemas similares en los que sea necesario comprobar si algún parámetro de entrada cumple una propiedad. Puedes suponer que la cumple (inicializar una variable booleana a true) y luego hacer lo que sea necesario para demostrar que no es así. En el momento en que tengas claro que la propiedad no se cumple, la variable te servirá como señal para abandonar bucles, etc...
Por ejemplo, rétate a ti mismo con estos otros problemas:
-Comprobar si una cadena pasada como parámetro a un método o función está escrita sólo con letras en mayúscula.
-Comprobar si una cadena empieza por 'http://' (sin usar funciones de manipular cadenas)
-Comprobar si una cadena está formada únicamente por cifras del '0' al '9' |