Sigueme en Twitter
«Anterior | Siguiente»

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.


Hay 13 comentarios:

  1. 18/05/2008Michoacano dice:

    Se me hace mucho trabajo para este problema. Si me preguntas a mi, yo lo haria con una simple formula… bueno dos xDDD(Pero nadie me pregunto=.

    Pero si lo que quieren es iterar, es mas fácil ir sumando de cinco en cinco, y de tres en tres, hasta que se llegue a 1000. Te ahorras mucho CPU.

  2. 18/05/2008dmind dice:
    1
    2
    3
    4
    5
    6
    7
    
    #!/usr/bin/env python
    suma, i = 0, 1
    while i &lt; 1000:
        if((i%3==0) or (i%5==0)):
            suma += i
        i+=1
    print suma

    yo adoro Python XD

  3. 18/05/2008christian dice:

    hola estaba viendo planeta linux y me tope con tu proyecto personal se ve interesante y pues que chido que te retes a ti mismo para mejoras, lei la solucion que propones pues como primera rspueta esta bien pero si lo que quieres es mejorar el desempeño de tu programa, recuerda que si puedes evitar las iteraciones en lo mayor posible es mejor
    y para este problema si analisas un poco como es funcion (la suma continua de un factor por una sucesion de numeros) pues te daras cuentas de que queda muy reducida y no vas a nesecitar iterar nada y la respuesta pues hasta puede quedar en muy pocas lineas bueno es solo recomendacion yo tambien estoy aprendiendo a ser mas habil con eso de programar y tienes razon solo practicando se puede mejorar nos vemos

  4. 18/05/2008xiam dice:

    Creo que el problema está mal planteado por que es ambiguo, por ejemplo, con el caso del 15 ya sabemos que es divisible entre 3 o 5, pero tengamos en cuenta que el ‘o lógico’ no es exclusivo, es decir, la suma de múltiplos de 3 o 5 menores de 20 podría ser:

    + 3 + 6 + 9 + 12 + 15 + 18 (los de 3)
    + 5 + 10 + 15 (los de 5)

    Donde el 15 estaría siendo sumado dos veces, la suma de dos 15 no alteraría las restricciones del problema ya que el ‘ó’ sigue siendo verdadero y la proposición se sigue cumpliendo.

    Suponiendo que ésta solución es válida también se puede resolver de ésta forma:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    #include <stdio.h>
     
    #define SUM(n) (n*(n+1)/2)
    #define TOP 1000
     
    int main() {
      printf("%d\n", 3*SUM((TOP-1)/3) + 5*SUM((TOP-1)/5));
      return 0;
    }

    Pero suponiendo la otra parte, es decir, que el ‘ó’ es en realidad un ‘ó’ exclusivo, se requeriría restar al resultado anterior los números que son múltiplos de 3 y 5, pero todos los números que son múltiplos de 3 y 5 son múltiplos de 3*5 = 15, entonces:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    #include <stdio.h>
     
    #define SUM(n) (n*(n+1)/2)
    #define TOP 1000
     
    int main() {
      printf("%d\n", 3*SUM((TOP-1)/3) + 5*SUM((TOP-1)/5) - 15*SUM((TOP-1)/15));
      return 0;
    }

    Y ese montón de cosas raras las baso en la siguiente demostración nerdosa:

    A y B números naturales.

    Se requiere la suma de todos los múltiplos de A menores a B.

    El mayor número natural menor que B es C = B - 1, entonces, los múltiplos de A pueden ser menores o iguales a C sin alterar el resultado, es decir

    A <= C < B

    Según el problema, la suma que buscamos se puede escribir como:

    A*1 + A*2 + A*3 + … + A*D

    Donde D es el número de los múltiplos de A menor o iguales a C, es decir, la parte entera de C / A, D = [[C/A]], donde [[]] es la función mayor entero (básicamente un floor())

    A*1 + A*2 + A*3 + … + A*D = A(1 + 2 + 3 + … + D)

    Pero sabemos que (1 + 2 + … + n) = n(n+1)/2, entonces

    A*1 + A*2 + A*3 + … + A*D = A(1 + 2 + 3 + … + D) = A(D(D+1)/2).

    La suma que estamos buscando es equivalente a

    A(D(D+1)/2)

    En el problema simplemente cambiamos las variables por las constantes apropiadas, y como en C, el número 5.8 necesariamente se trunca a 5 para seguir siendo int, no nos hace falta implementar una función [[]].

  5. 18/05/2008xiam dice:

    Bien, el #include debería ser a stdio.h pero wordpress me lo borró.

  6. 19/05/2008Michoacano dice:

    A eso me refería cuando dije que en una formula, pero creo que si esto fuera un concurso no aceptarían esa forma.

  7. 19/05/2008Yeru dice:

    @_@ No sabia de este proyecto Euler… La verdad que no, se ve bien interesante.. seguire viendo los problemas que publiques, notese que dije viendo y no resolviendo jajaja… Saludos.
    *a Yeru le da flojera programar asuntos matematicos por mas simple que sean…

    A ver a ver.. Cual es el otro problema que resolveras? Este estaba un poco facil… :)

  8. 21/05/2008Jesús dice:

    mira tú, que interesante, me pondré a resolverlos… pero este ya no vale porque ya lo resolviste así que mejor lo copypasteo ñ_ñ

  9. 21/05/2008pablasso dice:

    @Michoacano Tienes mucha razón, se itera mucho menos haciéndolo por partes y en rangos grandes si que valdrá la pena. A la próxima no te guardes la formula, en los concursos todo se vale mientras llegues al resultado ;-)

    @dmind Gracias! siempre es interesante verlo en otros lenguajes

    @xiam Me gusto mucho tu forma de resolver el problema y mucho mas tu forma de explicarlo, gracias por tomarte el tiempo

    @Yeru Fáciles y difíciles, ahí vamos de uno en uno :-)

    @Jesús .. Webón ¬¬

  10. 21/05/2008RalPh dice:

    jajaja, acaso apenas vas empesando con C++?
    Eso es de rutina prima semanal

  11. 21/05/2008pablasso dice:

    Es C no C++.

    No hay necesidad de ponerse como anónimo, precisamente esa es una de las motivaciones, desde clases en el TSU (unos 6 años) que no toco C y ese es uno de los propósitos, que se me quite lo wey. No me ofendo shinga :-)

  12. 13/06/2008Geranio Bigotes dice:

    Otra respuesta al problema en python, usando comprensión de listas, espero que les resulte interesante tb.

    sum([n for n in range(1,1000) if n % 3 == 0 or n % 5 == 0])

    Eso es todo :P, bien cortito… Saludos!

  13. 15/06/2008pablasso dice:

    Gracias Geranio!

Escribe tu comentario:

¿Escribiste código?


  Los mas frikis pueden suscribirse a los comentarios por RSS.