Sigueme en Twitter

Archivo de artículos en la categoría "Proyecto Euler"

Ir al inicio

El Proyecto Euler: Problema 6

19/02/2009

El Proyecto Euler es una serie de problemas de programación, si quieres enterarte de que va esto, lee la introducción.

Problema 6

La suma de los cuadrados de los 10 primeros números naturales es,
1^(2) + 2^(2) + … + 10^(2) = 385

El cuadrado de la suma de los 10 primeros números naturales es,
(1 + 2 + … + 10)^(2) = 55^(2) = 3025

Por lo tanto la diferencia entre la suma de los cuadrados de los 10 primeros números naturales y el cuadrado de la suma es 3025 – 385 = 2640.

Encuentra la diferencia entre la suma de los cuadrados de los primeros 100 números naturales y el cuadrado de la suma.

Respuesta “programador ocioso”. Iteramos sobre 100 números tal cual dice el problema, primero calculando la suma de los cuadrados y enseguida el cuadrado de la suma.

Hacemos la resta final y listo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#define LIMIT 100
 
int main()
{
	int i, sum = 0, square = 0;
 
	for ( i = 1; i <= LIMIT; i++ )
        {
		sum += i * i;
		square += i;
	}
 
	square *= square;
 
	printf("%d\n", square - sum);
	return 0;
}

Program exited with code #0 after 0.16 seconds.

10 Comentarios

El Proyecto Euler: Problema 5

16/02/2009

El Proyecto Euler es una serie de problemas de programación, si quieres enterarte de que va esto, lee la introducción.

Problema 5

Espero que Michoacano y los señores de Kobol me pueda perdonar, pero seguiré con la regla de «un minuto» para llegar a los problemas retadores en que ya no es posible las soluciones de fuerza bruta. Los siguientes dos son sencillos de resolver sino te pones a tratar de optimizarlos, pero el 7 me llamó la atención porque trata de nuevo de números primos y ese si trataré de optimizarlo. En fin, les paso de rapidito las soluciones cochinas de 5 y 6.

2520 es el número mas pequeño que puede ser dividido por cada uno de los números del 1 al 10 sin dejar residuos.

¿Cuál es el número mas pequeño perfectamente divisible -sin residuos- por todos los números del 1 al 20?

Como nos están pidiendo el mínimo común múltiplo de 20 números, la solución de fuerza bruta es bastante simple. Hacemos un bucle donde probamos número por número entre cada una de las opciones, si nos deja residuo entonces lo descartamos y vamos por el siguiente. Así hasta encontrar el indicado.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#define LIMIT 20
 
main()
{
	int flag = 1, n = 1, i;
 
	while ( flag )
	{
		n++;
 
		for ( i = 1; i <= LIMIT; i++ )
		{
			if ( (n % i) != 0 ) {
				break;
			}
 
			flag = i == LIMIT ? 0 : 1;
		}
	}
 
	printf("%d\n", n);
	return 0;
}

Mejoras

Muchísimas.

Leí que se pueden sacar los factores primos de cada unos de los números de 1 al 20 y entonces el producto de ellos nos daría el resultado. Hacer eso sería una solución mucho -pero mucho- mas rápida y mejor que esta.

3 Comentarios

El Proyecto Euler: Problema 4

6/02/2009

El Proyecto Euler es una serie de problemas de programación, si quieres enterarte de que va esto, lee la introducción.

Debo notar que he cambiado las reglas. Ahora me voy a preocupar mas por sacar la solución -menos cochina posible- que porque mi “algoritmo sea lo mas eficiente posible”. Esto es porque simplemente no me puedo dar el tiempo para ello y quiero avanzar con los ejercicios algún día. Espero me ayuden en los comentarios a mejorar las soluciones :-)

Problema 4

Un número capicúa se lee de la misma manera en ambas direcciones. El capicúa mas grande resultante del producto de dos números de dos dígitos es 9009 = 91 x 99.

Encuentra el mayor capicúa resultante del producto de dos números de tres dígitos.

Aquí tenemos dos variables cuyo valor puede ir desde 100 hasta 999 -todos los números de 3 dígitos-, es un rango realmente pequeño así que iremos con el viejo y sucio conocido método de fuerza bruta y probaremos cada una de las opciones.

Es decir; Un par de bucles para probar que cada resultado de las multiplicaciones de números de 3 dígitos, sea o no un número capicúa. Si el resultado es positivo entonces verificamos que sea el más grande que hemos encontrado hasta el momento y lo guardamos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
main()
{
	int i, j, x, r = 0;
 
	for ( i = 999; i > 99; i-- )
	{
		for ( j = 999; j > 99; j-- )
		{
			x = i * j;
 
			if ( is_palindrome(x) ) {
				r = x > r ? x : r;
			}
		}
	}
 
	printf("%d", r);
	return 0;
}

Para la función que verifica que el número sea capicúa podemos asumir -debo decir, suciamente- que el resultado que buscamos va a estar compuesto de 6 dígitos, así que nuestro objetivo sera descomponer el número a probar en un arreglo que tenga 6 elementos.

A notar que con la operación mod 10 podemos tomar el último dígito de un número y haciendo una división sobre 10 podemos eliminar dicho dígito del número, porque ya lo hemos guardado.

Al ya tener los 6 dígitos guardados podemos comparar cada uno de los índices opuestos para ver que sean iguales.

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int is_palindrome(int number)
{
	int i = MAX - 1, digits[MAX];
 
	while ( i >= 0 )
	{
		digits[i] = number % 10;
		number = number / 10;
		i--;
	}
 
	if ( digits[0] != digits[5] || digits[1] != digits[4] || digits[2] != digits[3] ) {
		return 0;
	}
 
	return 1;
}

Voila! Esto ya resuelve nuestro problema y lo hace en un tiempo razonable. Pero.. siempre hay un pero..

Si te sientes sucio utilizando una función que depende de que nuestro número contenga exactamente 6 dígitos, entonces podemos sustituirla por una función que simplemente toma un numero y lo devuelve al revés para poder checarlo directamente con el original.

Esta función se basa en el mismo principio que utilizamos antes con el operador mod y las divisiones. Extraer y eliminar el último dígito, solo que ahora lo vamos acumulando en otra variable -agregando nuevos dígitos al multiplicar por 10- para regresarlo de revés.

21
22
23
24
25
26
27
28
29
30
31
32
33
int reversed(int number)
{
	int reversed = 0;
 
	while (number > 0)
	{
		 reversed = reversed * 10;
		 reversed = reversed + number % 10;
		 number = number / 10;
	}
 
	return reversed;
}

Y entonces podemos sustituir la llamada de la is_palindrome por reversed

11
12
13
			if ( reversed(x) == x ) {
				r = x > r ? x : r;
			}

La primera función es un poco más rápida, pero la diferencia son apenas un par de milésimas. Ambas me parece que terminan en un tiempo aceptable.

real 0m0.070s
user 0m0.068s
sys 0m0.008s

Mejoras

Creo que el mayor problema con esta solución es el bucle a fuerza bruta, no es necesario probar cada combinación ya que por lo menos creo que dos cosas se deben de poder evitar:

  1. No repetir multiplicaciones. 999 x 555 = 555 x 999. Actualización: Victor Bracco ayudó con esta solución en los comentarios, los tiempos de ejecución se cortan por la mitad :-)
  2. Se deberían de evitar las multiplicaciones de números en que ambas variables sean con números bajos. Con solo calcular cual es la mínima combinación que nos resulta en productos de 6 dígitos nos evitaría operaciones innecesarias. Actualización: Victor dio con esta solución también, con ambas mejoras el programa tarda apenas 0.002s :-)

Actualización: Victor dio ahora una solución que mejora los ciclos externos necesitados :-)

17 Comentarios

El Proyecto Euler: Problema 3

6/10/2008

El Proyecto Euler es una serie de problemas de programación, si quieres enterarte de que va esto, lee la introducción.

Problema 3

Los factores primos de 13195 son 5, 7, 13 y 29.

¿Cual es el factor primo mas grande del número 600851475143?

Los factores primos de un entero positivo, son los números primos que dividen a ese entero exactamente, es decir, sin dejar residuos. Este problema es comúnmente utilizado para criptografía, ya que con los métodos matemáticos y poder de computo que tenemos actualmente no es posible desencriptar llaves grandes en un tiempo razonablemente practico (decenas de años, miles de años, etc).

Una forma de resolver este problema es con un bucle que se vaya dividiendo el número deseado “x” entre números primos, cuando tiene un resultado sin residuo entonces “x” toma ese valor.

Conforme avanzamos ciclos “x” se va reduciendo y nuestro bucle lo va alcanzando. Cuando se alcanzan, eso indica nuestro factor primo mas grande.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
main()
{
    long long i, num = 600851475143LL;
 
    for (i = 3; i != num; i += 2)
    {
        if (num % i == 0) {
            num = num / i;
        }
    }
 
    printf ("El resultado es %d", num);
    return 0;
}

Si bien esto hace mas de tres mil entradas al bucle, es suficientemente eficiente para terminar en 1 milisegundo, lo que me parece razonable si estamos hablando de calcular para un número del orden 10^11 (cientos de miles de millones).

$ time ./003
El resultado es 6857
real 0m0.001s
user 0m0.000s
sys 0m0.000s

Hay muchos métodos de factorización modernos que son una gran mejora a los viejos métodos de factorización de Fermat y son bastante buenos en números de orden de magnitud mayores al que vimos, como el algoritmo rho de Pollard, Quadratic sieve o General number field sieve. (Lo primero que intente fue utilizar el rho de Pollard, pero me estaba costando un hue mucho tiempo hacerlo en C).

Así que si tienes que trabajar sobre números enormes, te vendría bien utilizar uno de estos algoritmos.

10 Comentarios

El Proyecto Euler: Problema 2

22/05/2008

El Proyecto Euler es una serie de problemas de programación, si quieres enterarte de que va esto, lee la introducción.

Problema 2

Cada nuevo termino en la secuencia de Fibonacci es generada agregando los dos términos previos. Comenzando con 1 y 2, los primeros 10 términos serán:

1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …

Encuentra la suma de todos los términos pares en una secuencia que no sobrepase los 4 millones.

Para resolverlo de la forma bruta, hay que construir la secuencia de Fibonnaci solamente agregando una condición donde vamos agregando una sumatoria de los números pares, esa verificación es donde muy probablemente se pueda optimizar esto. Mañana salgo de viaje, hasta fines de la siguiente semana revisare que se pudo hacer.

Por cierto, hubiera jurado que el problema antes estaba escrito con el límite de 1 millón, así tenía guardada la solución, me pregunto ¿porque lo habrán cambiado? por lo pronto el siguiente que haga sera completamente nuevo, los dos primeros ya los había resuelto cuando me registre a la página.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
main()
{
    int sum = 0, a = 1, b = 1, c = 0;
    while ( c < 4000000 )
    {
        c = a + b;
        a = b;
        b = c;
 
        if ( c % 2 == 0 ) {
            sum += c;
        }
    }
 
    printf("resultado: %d\n", sum);
    return 0;
}

Conclusión

Lo mas costoso de esta solución es la comprobación de que los números sean pares, y revisando otras soluciones hay muchas formas de evitar esta comprobación.

Como bien notaron Michoacano y Xiam, resulta que si revisamos la secuencia, podemos notar que cada tercer número es par y ya sabiéndolo de antemano nos podemos evitar la comprobación y sumar solo cada tercer número.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
main()
{
    int sum = 0, a = 1, b = 1, c = a + b;
    while ( c < 4000000 )
    {
        sum += c;
        a = b + c;
        b = c + a;
        c = a + b;
    }
 
    printf("resultado: %d\n", sum);
    return 0;
}

Nuevamente, esto ayudara realmente en rangos mucho mas grandes que el que tenemos.

12 Comentarios

El Proyecto Euler: Problema 1

18/05/2008

El Proyecto Euler es una serie de problemas de programación, si quieres enterarte de que va esto, lee la introducción.

Problema 1

Si listamos todos los números naturales debajo de 10 que son múltiplos de 3 o 5, tenemos 3, 5, 6 y 9. La suma de estos múltiplos es 23.

Encuentra la suma de todos los múltiplos de 3 o 5 debajo de 1000.

Este problema lo he visto como de los ‘recomendados’ para poner en las entrevistas para trabajo, simple y rápido, te dice claramente las 3 condiciones a evaluar.

El operador % regresa el residuo de una división, por lo que podemos iterar de uno en uno hasta el límite superior (1000) y verificar si cada número es perfectamente divisible (el residuo es 0) entre 3 o 5, entonces lo podemos almacenar.

Resuelto en C.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
main()
{
    int sum = 0, i;
    for ( i = 1; i < 1000; i++ )
    {
        if ( (i % 3 == 0) || (i % 5 == 0) ) {
            sum += i;
        }
    }
 
    printf("El resultado es: %d", sum);
}

Conclusión

Comparando con resultados del Proyecto Euler, si bien la solución de fuerza bruta que hice no esta mal, si la pregunta exigiera un limite con cantidades grandes no es eficiente hacer un loop comparando resultado por resultado, sino que es mejor calcular las operaciones por separado.

La suma de números divisibles por 3, la suma de números divisibles por 5 y restando finalmente los números divisibles por 15 que sino tendríamos los divisibles comunes de 3 y 5 contados 2 veces. Se puede evadir por completo cualquier loop si utilizamos progresiones aritméticas.

Para poner un poco en contraste, haciendo benchmark (utilizando gprof) de mi solución para el limite de 1 billón (si si, un uno seguido de 9 ceros), este laptop tarda 15.68 segundos.

En cambio la siguiente versión (versión de sandippal en el foro) que utiliza este principio, tarda menos de .01 segundo, ¿bastante no?.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#define NUM 999999999
 
main()
{
  unsigned long long mult3  = NUM / 3;
  unsigned long long mult5  = NUM / 5;
  unsigned long long mult15 = NUM / 15;
  unsigned long long sum3, sum5, sum15;
 
  sum3 = 3  * mult3  * (mult3 + 1) / 2;
  sum5 = 5  * mult5  * (mult5 + 1) / 2;
  sum15 = 15 * mult15 * (mult15 + 1) / 2;
 
  printf("resultado: %lld\n", sum3 + sum5 - sum15);
}

Gracias en especial al comentario de Xiam, grande su explicación.

16 Comentarios

El Proyecto Euler

18/05/2008

¿Que es el Proyecto Euler?

El Proyecto Euler es una serie de retadores problemas de matemática/programación que requerirán mas que un poco de conocimientos de matemáticas para resolver. Aunque las matemáticas te ayudarán a tener métodos elegantes y eficientes, el uso de la computadora y habilidades de programación son requeridos para resolver la mayoría de los problemas.

Desde hace tiempo he querido avanzar con ellos y mi desidia no me ha dejado, así que ahora publicaré los problemas que vaya resolviendo (si, uno por uno) y de paso me doy motivación extra.

También es una perfecta oportunidad para que la vasta fauna del internet pueda corregir mi imperfecta manera de programar, así que cualquier corrección no duden en hacerla, mientras mas me equivoco mas aprendo.

Algunas cosas que notar:

  • Siempre agradeceré que revisen el problema y den su opinión, planeo equivocarme bastante. Los problemas se encuentran si buscamos en el internets o en la página del proyecto, pero el punto es pensar un poco y hacerlo uno mismo, lo demás es perder el tiempo.
  • Siempre buscare que el programa sea mas eficiente, y por eficiente se entiende que el algoritmo ejecute en menos ciclos de maquina, por lo tanto en menos tiempo.
  • He notado que no puedo darme el lujo de tardar cuatro veces mas resolviendo algo por intentar hacerlo lo mas eficiente posible, así que ahora voy a preocuparme menos por el rendimiento y mas por la respuesta basándome en la «regla de un minuto» del mismo proyecto euler, un programa siempre debe de tardar menos de un minuto en ejecución, otro caso es inaceptable. Esto es porque necesito aprovechar mejor mi tiempo, espero que me ayuden en los comentarios a mejorar las soluciones.
  • Siempre preferiré código legible que escribir en el menor número de lineas ininteligibles posible.
  • Los quiero hacer todos en C -ANSI C99- para aprender, así tarde tres veces más y cometa muchos errores como n00b que soy, no me importa, desde la carrera técnica no lo utilizo y ya dan ganas :-)
  • Si planeas hacerlos por tu cuenta, no leas esto antes! básicamente es un spoiler.

Listado de problemas:

  • Problema 1 – Suma todos los números naturales menores a 1000 que son múltiplos de 3 o 5.
  • Problema 2 – Encuentra la suma de todos los números pares en una secuencia de Fibonacci que no pase los 4 millones.
  • Problema 3 – Encuentra el factor primo mas grande de un número compuesto.
  • Problema 4 – Encuentra el mayor producto capicúa de dos números de tres dígitos.
  • Problema 5 – ¿Cuál es el número mas pequeño divisible por cada uno de los números del 1 al 20?
  • Problema 6 – ¿Cuál es la diferencia entre la suma de los cuadrados y el cuadrado de las sumas?
3 Comentarios