Tomé el Problema # 12 del Proyecto Euler como un ejercicio de programación y para comparar mis implementaciones (seguramente no óptimas) en C, Python, Erlang y Haskell. Para obtener tiempos de ejecución más altos, busco el primer número de triángulo con más de 1000 divisores en lugar de 500 como se indica en el problema original.
El resultado es el siguiente:
C:
lorenzo@enzo:~/erlang$ gcc -lm -o euler12.bin euler12.c
lorenzo@enzo:~/erlang$ time ./euler12.bin
842161320
real 0m11.074s
user 0m11.070s
sys 0m0.000s
Pitón:
lorenzo@enzo:~/erlang$ time ./euler12.py
842161320
real 1m16.632s
user 1m16.370s
sys 0m0.250s
Python con PyPy:
lorenzo@enzo:~/Downloads/pypy-c-jit-43780-b590cf6de419-linux64/bin$ time ./pypy /home/lorenzo/erlang/euler12.py
842161320
real 0m13.082s
user 0m13.050s
sys 0m0.020s
Erlang:
lorenzo@enzo:~/erlang$ erlc euler12.erl
lorenzo@enzo:~/erlang$ time erl -s euler12 solve
Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.7.4 (abort with ^G)
1> 842161320
real 0m48.259s
user 0m48.070s
sys 0m0.020s
Haskell
lorenzo@enzo:~/erlang$ ghc euler12.hs -o euler12.hsx
[1 of 1] Compiling Main ( euler12.hs, euler12.o )
Linking euler12.hsx ...
lorenzo@enzo:~/erlang$ time ./euler12.hsx
842161320
real 2m37.326s
user 2m37.240s
sys 0m0.080s
Resumen:
- C: 100%
- Python: 692% (118% con PyPy)
- Erlang: 436% (135% gracias a RichardC)
- Haskell: 1421%
Supongo que C tiene una gran ventaja ya que usa largos para los cálculos y no enteros de longitud arbitraria como los otros tres. Además, no necesita cargar un tiempo de ejecución primero (¿los otros?).
Pregunta 1:
¿Erlang, Python y Haskell pierden velocidad debido al uso de enteros de longitud arbitraria o no, siempre que los valores sean menores MAXINT
?
Pregunta 2: ¿Por qué Haskell es tan lento? ¿Hay una bandera del compilador que apaga los frenos o es mi implementación? (Esto último es bastante probable ya que Haskell es un libro con siete sellos para mí).
Pregunta 3: ¿Puede ofrecerme algunas sugerencias sobre cómo optimizar estas implementaciones sin cambiar la forma en que determino los factores? Optimización de cualquier forma: más agradable, más rápida, más "nativa" del idioma.
EDITAR:
Pregunta 4: ¿Mis implementaciones funcionales permiten LCO (optimización de última llamada, también conocida como eliminación de recursión de cola) y, por lo tanto, evitan agregar marcos innecesarios en la pila de llamadas?
Realmente intenté implementar el mismo algoritmo lo más similar posible en los cuatro idiomas, aunque tengo que admitir que mi conocimiento de Haskell y Erlang es muy limitado.
Códigos fuente utilizados:
#include <stdio.h>
#include <math.h>
int factorCount (long n)
{
double square = sqrt (n);
int isquare = (int) square;
int count = isquare == square ? -1 : 0;
long candidate;
for (candidate = 1; candidate <= isquare; candidate ++)
if (0 == n % candidate) count += 2;
return count;
}
int main ()
{
long triangle = 1;
int index = 1;
while (factorCount (triangle) < 1001)
{
index ++;
triangle += index;
}
printf ("%ld\n", triangle);
}
#! /usr/bin/env python3.2
import math
def factorCount (n):
square = math.sqrt (n)
isquare = int (square)
count = -1 if isquare == square else 0
for candidate in range (1, isquare + 1):
if not n % candidate: count += 2
return count
triangle = 1
index = 1
while factorCount (triangle) < 1001:
index += 1
triangle += index
print (triangle)
-module (euler12).
-compile (export_all).
factorCount (Number) -> factorCount (Number, math:sqrt (Number), 1, 0).
factorCount (_, Sqrt, Candidate, Count) when Candidate > Sqrt -> Count;
factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;
factorCount (Number, Sqrt, Candidate, Count) ->
case Number rem Candidate of
0 -> factorCount (Number, Sqrt, Candidate + 1, Count + 2);
_ -> factorCount (Number, Sqrt, Candidate + 1, Count)
end.
nextTriangle (Index, Triangle) ->
Count = factorCount (Triangle),
if
Count > 1000 -> Triangle;
true -> nextTriangle (Index + 1, Triangle + Index + 1)
end.
solve () ->
io:format ("~p~n", [nextTriangle (1, 1) ] ),
halt (0).
factorCount number = factorCount' number isquare 1 0 - (fromEnum $ square == fromIntegral isquare)
where square = sqrt $ fromIntegral number
isquare = floor square
factorCount' number sqrt candidate count
| fromIntegral candidate > sqrt = count
| number `mod` candidate == 0 = factorCount' number sqrt (candidate + 1) (count + 2)
| otherwise = factorCount' number sqrt (candidate + 1) count
nextTriangle index triangle
| factorCount triangle > 1000 = triangle
| otherwise = nextTriangle (index + 1) (triangle + index + 1)
main = print $ nextTriangle 1 1
Euler12[x_Integer] := Module[{s = 1}, For[i = 2, DivisorSigma[0, s] < x, i++, s += i]; s]
. ¡Hurra!