En ocasiones al estar programando algo, la intuición nos dice que básicamente traemos un desmadre. No sabemos con exactitud cómo encontrar una solución más elegante y pensamos: seguro hay una mejor manera.

Esto no es del todo paranóico o perfeccionista. No siempre es posible encontrar la forma más rápida o creativa de programar algo, pero a veces, si investigamos un poco, podemos toparnos con un ángulo distinto.

Pongamos dos ejemplos; ambos sencillos. El punto aquí es ver cómo resolveríamos cada uno.


Suma de números consecutivos

Primer ejemplo: obtener la suma de números enteros del 1 a n. Bastante sencillo. Podemos hacer un ciclo e ir acumulando la sumatoria:

$suma = 0;

for($c=1; $c<= 100; $c++) {
    $suma += $c;
}

echo $suma; // imprime 5050

Funciona: Imprime el resultado correcto. Pero no sé, me parece innecesario solamente para sumar una serie de valores. Una alternativa es evitar utilizar un ciclo y aprovechar las funciones de PHP. En ese caso podemos utilizar array_sum(), pero esta función requiere de un arreglo. Bueno, para eso podemos utilizar otra función del lenguaje: range(), que genera un arreglo con los valores mínimo y máximo indicados. Esta solución quedaría de la siguiente manera:

echo array_sum(range(1, 100)); // imprime 5050

El mismo resultado, en una sola línea. Aunque, claro, esto no se trata de reducir el número de líneas. El código es más corto que el anterior, pero aunque hemos delegado al lenguaje el trabajo de sumatoria, aún así generamos un arreglo al utilizar range().

Cómo mencionaba antes, no siempre será posible encontrar una solución perfecta. En programación siempre vamos a tener que decidir que es más importante para la aplicación: que se ejecute con rapidez, que el código sea entendible... entre otras decisiones.

Para este ejemplo en particular, existe una fórmula que permite sumar la serie de números. Esta fórmula se llama Suma de Gauss. La forma simplificada de la fórmula es la siguiente:

n (n +1) / 2

Utilicémosla en un ejemplo de código:

$n = 100;

$sum = (($n + 1) * $n) / 2;

echo $sum; //imprime 5050

Ok, ok. Esta solución también es correcta, pero la solución anterior con array_sum() y range() sigue siendo más corta y entendible.

De acuerdo, pero no tan rápido, amigos. Resulta que la solución que usa range() tiene un "pequeño" problema. Crear un arreglo con valores del 1 al 100 es asunto trivial, pero si el valor superior es muy grande, ahí es cuando todo se viene abajo.

Supongamos que deseamos obtener la suma de números del 1 al 9223399 utilizando la solución con range():

echo array_sum(range(1, 9223399));

Si ejecutamos el código es posible que obtengamos el siguiente mensaje de error:

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 536870920 bytes)

¡Ups, parece que se agotó la memoria disponible para PHP! ¡Por sumar un rango de números! En este caso la memoria disponible es de 134217728 (128 Mb) y nuestro código requiere 536870920 bytes (512 Mb). Así es, 512 Megabytes para sumar una secuencia de números.

Ahora probemos obtener la suma del mismo rango pero ahora utilizando la fórmula de Gauss:

$n = 9223399;

$sum = (($n + 1) * $n) / 2;

echo $sum; // imprime 42535549168300

Funciona, y no requiere 512 Mb de memoria.

Entonces, ¿siempre hay que buscar fórmulas? No, pero habrá que decidir qué es más importante. O en casos como el de este ejemplo, quizá no tendremos opción si la solución actual afecta al rendimiento.


Conversión de Bytes

En el ejemplo de suma de números recibimos un error cuando se agotó la memoria disponible. La cantidad de memoria se mostraba en bytes. Esto nos lleva a nuestro segundo ejemplo. Si 1024 bytes equivale a 1 Kilobyte, 1024 Kilobytes equivale a 1 Megabyte, y así sucesivamente, ¿cómo haríamos una función que convierta de bytes a su equivalente?

La solución más lógica quizá, consiste en determinar el rango de valores de la cantidad de bytes recibida y convertirla:

function convertBytes($bytes) {
    if($bytes == 0) {
        return '0 Bytes';
    } else if($bytes > 0 && $bytes < 1024) {
        return $bytes . ' Bytes';
    } else if($bytes > 1024 && $bytes < 1048576) {
        return ($bytes / 1024) . ' KB';
    } else if($bytes > 1048576 && $bytes < 1073741824) {
        return ($bytes / 1048576) . ' MB';
    } else if($bytes > 1073741824) {
        return ($bytes / 1073741824) . ' GB';
    }
}

Esta solución funciona pero podemos ver que es bastante desordenada y contiene varios números mágicos, y eso que solamente maneja conversiones hasta el rango de los Gigabytes. Pero al igual que en el ejemplo de suma de números, existe una fórmula que podemos usar para conversión de bytes:

function convertBytes($bytes) {

    if($bytes == 0) {
        return '0 Byte';
    }

    $k = 1024;
    $sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];

    $i = floor(log($bytes) / log($k));
    return round($bytes / pow($k, $i), 2) . ' ' . $sizes[$i];

}

Esta solución utiliza las funciones log() (logaritmo natural) y pow() (elevar a una potencia) para lograr su cometido. La fórmula, a grandes rasgos, funciona de la siguiente manera: la parte que utiliza logaritmo natural sirve para determinar a qué rango o potencia pertenece la cantidad de bytes proporcionada. Después hay que dividir la cantidad de bytes entre el número de bytes que representa una unidad de la potencia a la que pertenece. Por ejemplo, si sabemos que pertenece al rango de los Kilobytes, entonces hay que dividir entre 1024, ya que 1024 bytes equivale a 1 Kilobyte. La función pow() se usa para elevar la base ($k) a la potencia calculada ($i) y así poder realizar la división en términos de bytes.

Como se puede ver, la solución es más corta, pero quizá no tan fácil de entender como la solución que utiliza la serie de condiciones IF. La ventaja en este caso es que es más fácil agregar nuevas potencias al arreglo $sizes, siempre y cuando se agreguen en el orden correcto. En el caso de la solución basada en condiciones, hay que conocer los valores en bytes de cada potencia para poder realizar los cálculos correctamente.


¿Cuál es el punto? ¿Hay que buscar fórmulas para todo?

Como dije antes, no hay que buscar la fórmula o solución perfecta. Hay que buscar alternativas y decidir qué consideramos "suficientemente bueno". Si buscamos diferentes ángulos a un problema o vemos cómo lo han resuelto otras personas, es muy probable que encontremos una mejor manera.