Pesadillas con If/else
Baltasar García Perez-Schofield
Posted on September 18, 2024
De cuando en cuando, "salta" en internet código como el siguiente.
Conversiones de caracteres a número y viceversa
char chrFromInt(int digit)
{
char toret;
if ( digit == 0 ) {
toret = '0';
}
else
if ( digit == 1 ) {
toret = '1';
}
else
if ( digit == 2 ) {
toret = '2';
}
else
if ( digit == 3 ) {
toret = '3';
}
else
if ( digit == 4 ) {
toret = '4';
}
else
if ( digit == 5 ) {
toret = '5';
}
else
if ( digit == 6 ) {
toret = '6';
}
else
if ( digit == 7 ) {
toret = '7';
}
else
if ( digit == 8 ) {
toret = '8';
}
else
if ( digit == 9 ) {
toret = '9';
} else {
fprintf( stderr, "ERROR: digit no es 0-9: %d\n", digit );
exit( -1 );
}
return toret;
}
Este auténtico anti-patrón (antipattern), puede tomar varias formas con distintos requerimientos. La variante más común, sin embargo, sustituye if/else por switch.
char chrFromInt(int digit)
{
char toret;
switch( digit ) {
case 0:
toret = '0';
break;
case 1:
toret = '1';
break;
case 2:
toret = '2';
break;
case 3:
toret = '3';
break;
case 4:
toret = '4';
break;
case 5:
toret = '5';
break;
case 6:
toret = '6';
break;
case 7:
toret = '7';
break;
case 8:
toret = '8';
break;
case 9:
toret = '9';
break;
default:
fprintf( stderr, "ERROR: digit no es 0-9: %d\n", digit );
exit( -1 );
}
return toret;
}
El código anterior sería C. En Python, la función anterior sería algo como:
def chr_from_int(digit):
if digit == 0:
toret = '0'
elif digit == 1:
toret = '1'
elif digit == 2:
toret = '2'
elif digit == 3:
toret = '3'
elif digit == 4:
toret = '4'
elif digit == 5:
toret = '5'
elif digit == 6:
toret = '6'
elif digit == 7:
toret = '7'
elif digit == 8:
toret = '8'
elif digit == 9:
toret = '9'
else:
raise Error("digit no es 0-9, sino " + str(digit))
return toret
Y la versión utilizando match
(recientemente añadido al lenguaje), sería:
def chr_from_int(digit):
match digit:
case 0:
toret = '0'
case 1:
toret = '1'
case 2:
toret = '2'
case 3:
toret = '3'
case 4:
toret = '4'
case 5:
toret = '5'
case 6:
toret = '6'
case 7:
toret = '7'
case 8:
toret = '8'
case 9:
toret = '9'
case _:
throw Error("digit no es 0-9, sino " + str(digit))
return toret
El uso de esta función sería bastante simple (código C):
for(int i = 0; i < 12; ++i) {
printf( "%c\n", intFromDigit( i ) );
}
Y la salida sería la siguiente:
0
1
2
3
4
5
6
7
8
9
Runtime error
ERROR: digit no es 0-9: 11
¿Y qué es lo que podemos hacer en este caso? Como siempre, podemos investigar más profundamente. ¿Por qué hacerlo? Porque escribiendo este programa, estamos repitiendo código. Siempre que repetimos código, significa que estamos en un problema.
Todas las codificaciones de caracteres (UTF-16, UTF-8, ISO-8859-1...) se apoyan en el código ASCII para representar los caracteres básicos, como se puede ver en la siguiente tabla.
Si nos fijamos, los caracteres están representados en el código ASCII en orden alfabético, así como los dígitos lo están de menor a mayor. Esto es verdaderamente importante; no que el carácter 'a' sea el código nº 97, o el '0' el 48. A pesar de que el código ASCII está tan extendido que encontrar un hardware que no lo use directa o indirectamente, sea difícil, aún puede suceder. Así que es mejor utilizar las características del propio lenguaje. Por ejemplo, en C, 'a'
devuelve directamente el código numérico asociado (es decir, 97 en ASCII), al igual que ord('a')
hace lo mismo en Python (y chr(97)
lo contrario).
Así que es cuestión de sumar al número que nos pasan, el valor de '0'.
char chrFromInt(int digit)
{
if ( digit < 0
|| digit > 9 )
{
fprintf( stderr, "ERROR: digit no es 0-9: %d\n", digit );
exit( -1 );
}
return '0' + digit;
}
En el caso de Python, como vimos en los ejemplos anteriores, también se puede repetir código. Pero el soporte de Python de estructuras de datos complejas (como diccionarios o listas), con un uso muy sencillo, nos puede llevar a abusar de ellas. Un ejemplo se puede ver a continuación.
def chr_from_int(digit: int) -> str:
sust = [0: '0', 1: '1', 2: '2', 3: '3', '4': '5', 6: '6', 7: '7', 8: '8', 9: '9']
toret = sust.get(digit)
if not toret:
raise Error("digit no es 0-9, sino " + str(digit))
return toret
Es un poco mejor, porque no repetimos código, pero... ¿Cuáles son las claves de ese diccionario sust? El rango 0 a 9 sería el acceso normal de cualquier lista, así que en realidad se puede simplificar a:
def chr_from_int(digit: int) -> str:
sust = ['0', '1', '2', '3', '4', '6', '7', '8', '9']
if (digit < 0
or digit > 9):
throw Error("digit no es 0-9, sino " + str(digit))
return sust[digit]
De nuevo está mejor que lo que teníamos antes, pero... lo único que tenemos es una lista con los dígitos ordenados del 0 al 9 en formato de carácter... ya sabemos que... se puede hacer mucho mejor. No necesitamos de ninguna estructura de datos, ocupando memoria.
Así, en Python:
def chr_from_int(digit: int) -> str:
if (digit < 0
or digit > 9):
throw Error("digit no es 0-9, sino " + str(digit))
return chr(ord('0') + digit)
Leet speech
Podríamos querer convertir un texto cualquiera a leet speech, una forma de escribir que sería algo así como el "discurso" de los hackers.
La conversión del alfabeto natural a leet, consiste básicamente en sustituir los caracteres por dígitos (además de algunos otros caracteres). Está conversión está representada en la siguiente tabla.
Carácter | Dígito |
---|---|
a | 4 |
b | 8 |
c | ( |
d | 6 |
e | 3 |
f | | |
g | C- |
h | # |
i | 1 |
j | ] |
k | |< |
l | |_ |
m | [V] |
n | /V |
o | 0 |
p | |7 |
q | 9 |
r | I2 |
s | 5 |
t | 7 |
u | |
v | \/ |
w | \N |
x | >< |
y | `/ |
z | 2 |
Para que quede claro, queremos una función que, al llegarle un texto como "Baltasar", devuelva otro texto: "84|_7454I2"
.
Si utilizamos Python, podemos llegar a este código, parecido al que teníamos anteriormente.
def leet_from_str(s: str) -> str:
sust = {
'a': "4",
'b': "8",
'c': "(",
'd': "C-",
'e': "3",
'f': '|',
'g': "C-",
'h': "#",
'i': "1",
'j': "]",
'k': "|<",
'l': "|_",
'm': "[V]",
'n': "/V",
'o': "0",
'p': "|7",
'q': "9",
'r': "|2",
's': "5",
't': "7",
'u': "|_|",
'v': "\\/",
'w': "\\N",
'x': "><",
'y': "'/",
'z': "2",
}
toret = ""
for ch in s:
ch2= sust.get(ch)
if not ch2:
raise Error("ch no es a-z, sino " + str(digit))
toret += ch2
return toret
Con este código, print(leet_from_str("Baltasar"))
, nos devuelve 84|_7454|2
, como se esperaba.
De acuerdo, pero C no tiene soporte para diccionarios en su librería estándar. ¿Qué podemos hacer en un caso así?
Eso lo veremos en el siguiente capítulo...
Posted on September 18, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.