Ho preso il Problema n. 12 da Project Euler come esercizio di programmazione e per confrontare le mie (sicuramente non ottimali) implementazioni in C, Python, Erlang e Haskell. Per ottenere tempi di esecuzione più alti, cerco il primo numero di triangolo con più di 1000 divisori anziché 500 come indicato nel problema originale.
Il risultato è il seguente:
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
Pitone:
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
Sommario:
- C: 100%
- Python: 692% (118% con PyPy)
- Erlang: 436% (135% grazie a RichardC)
- Haskell: 1421%
Suppongo che C abbia un grande vantaggio in quanto utilizza long per i calcoli e numeri interi non arbitrari come gli altri tre. Inoltre, non è necessario caricare prima un runtime (fare gli altri?).
Domanda 1:
Erlang, Python e Haskell perdono velocità a causa dell'uso di numeri interi di lunghezza arbitraria o non finché i valori sono inferiori a MAXINT
?
Domanda 2: Perché Haskell è così lento? C'è una bandiera del compilatore che spegne i freni o è la mia implementazione? (Quest'ultimo è abbastanza probabile poiché Haskell è un libro con sette sigilli per me.)
Domanda 3: Potete offrirmi alcuni suggerimenti su come ottimizzare queste implementazioni senza cambiare il modo in cui determino i fattori? Ottimizzazione in qualsiasi modo: più bella, più veloce, più "nativa" della lingua.
MODIFICARE:
Domanda 4: Le mie implementazioni funzionali consentono LCO (ottimizzazione dell'ultima chiamata, ovvero l'eliminazione della ricorsione della coda) e quindi evitare l'aggiunta di frame non necessari nello stack di chiamate?
Ho davvero cercato di implementare lo stesso algoritmo il più simile possibile nelle quattro lingue, anche se devo ammettere che la mia conoscenza di Haskell ed Erlang è molto limitata.
Codici sorgente utilizzati:
#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]
. Evviva!