问题
I am not really satisfied with my F# solution to this problem because I can't find a beautiful & fast solution, but that's not the issue here. The issue is, I translated the solution to C# for the heck of it, and it is fast. Like, really fast, comparatively.
I can't figure out why. Yes, I've been to Reflector, C# code looks very similar, can't say I really understand IL but it looks kinda like the same. The only thing I can think of is performance of F# int[] against C# List.
So here it goes:
F#
module Euler023
let divisorsSum n =
let mutable sum = 1
let limit = (int (sqrt(float n)))
for i in [2..limit] do
if n%i=0 then sum <- sum+i+n/i
if (limit*limit)=n then sum-limit else sum
let isAbundant x = (divisorsSum x)>x
let abundants = [1..28123] |> List.filter isAbundant |> List.toArray
let domain = System.Collections.BitArray(28124)
let rec loopUntil i j =
if i=abundants.Length then ()
elif j=abundants.Length then loopUntil (i+1) (i+1)
else
let sum = abundants.[i]+abundants.[j]
if sum<28124 then
domain.Set(sum, true)
loopUntil i (j+1)
else
loopUntil (i+1) (i+1)
let solve = loopUntil 0 0
[1..28123] |> List.filter (fun x -> domain.Get(x)=false) |> List.sum
C#
static int divisorsSum(int n)
{
int sum = 0;
var limit = (int)Math.Sqrt(n);
for (int i=2;i<=limit;++i) if (n%i==0) sum += i + n/i;
if ((limit * limit) == n) return sum-limit;
return sum;
}
static List<int> getAbundants(int ceiling)
{
var ret = new List<int>();
for (int i = 1; i < ceiling; ++i) if (divisorsSum(i) > i) ret.Add(i);
return ret;
}
static void Main(string[] args)
{
var abundants = getAbundants(28124);
var bitField = new bool[28124];
for (int i = 0; i < abundants.Count; ++i)
for (int j = i; j < abundants.Count; ++j)
{
var sum = abundants[i] + abundants[j];
if (sum < 28124) bitField[sum] = true;
else break;
}
var total = 0;
for (int i = 0; i < 28124; ++i) if (bitField[i]==false) total += i;
}
Update
The project containing this code consists of a separate file for each problem (EulerXXX.fs) + the main program file. Main program file is as follows
module Program =
let stopWatch = System.Diagnostics.Stopwatch()
let mutable totalTime = System.TimeSpan()
let inline tick()
=
stopWatch.Stop();
totalTime <- totalTime.Add stopWatch.Elapsed
printfn " -> Elapsed: %2.2f sec Total: %2.2f s" stopWatch.Elapsed.TotalSeconds totalTime.TotalSeconds
stopWatch.Restart()
let _ =
stopWatch.Start()
printf "Euler001 solution: %A" Euler001.solve
tick()
printf "Euler002 solution: %A" Euler002.solve
tick()
printf "Euler003 solution: %A" Euler003.solve
tick()
printf "Euler004 solution: %A" Euler004.solve
tick()
printf "Euler005 solution: %A" Euler005.solve
tick()
printf "Euler006 solution: %A" Euler006.solve
tick()
printf "Euler007 solution: %A" Euler007.solve
tick()
printf "Euler008 solution: %A" Euler008.solve
tick()
printf "Euler009 solution: %A" Euler009.solve
tick()
printf "Euler010 solution: %A" Euler010.solve
tick()
printf "Euler011 solution: %A" Euler011.solve
tick()
printf "Euler012 solution: %A" Euler012.solve
tick()
printf "Euler013 solution: %A" Euler013.solve
tick()
printf "Euler014 solution: %A" Euler014.solve
tick()
printf "Euler015 solution: %A" Euler015.solve
tick()
printf "Euler016 solution: %A" Euler016.solve
tick()
printf "Euler017 solution: %A" Euler017.solve
tick()
printf "Euler018 solution: %A" Euler018.solve
tick()
printf "Euler019 solution: %A" Euler019.solve
tick()
printf "Euler020 solution: %A" Euler020.solve
tick()
printf "Euler021 solution: %A" Euler021.solve
tick()
printf "Euler022 solution: %A" Euler022.solve
tick()
printf "Euler023 solution: %A" Euler023.solve
tick()
printf "Euler024 solution: %A" Euler024.solve
tick()
printf "Euler059 solution: %A" Euler059.solve
tick()
printf "Euler067 solution: %A" Euler067.solve
tick()
stopWatch.Stop()
System.Console.ReadLine()
The output of the program is as follows:
Euler001 solution: 233168 -> Elapsed: 0.02 sec Total: 0.02 s
Euler002 solution: 4613732 -> Elapsed: 0.03 sec Total: 0.04 s
...
Euler022 solution: 871198282 -> Elapsed: 0.02 sec Total: 4.11 s
Euler023 solution: 4179871 -> Elapsed: 81.11 sec Total: 85.22 s
Euler024 solution: [2; 7; 8; 3; 9; 1; 5; 4; 6; 0] -> Elapsed: 0.01 sec Total: 85.23 s
...
Euler067 solution: [7273] -> Elapsed: 0.01 sec Total: 85.31 s
So the problem is not in project parameters. Also, if I copy code from Euler023 to Program it will run instantly. The question is, why does this slowdown happen only for this problem?
回答1:
Your F# version isn't slow at all; it takes 0.44 seconds in F# Interactive on my machine. I don't know how you could observe such a slowness (30.5 seconds). If you compile and run the code, make sure that you're in Release mode and turning on optimization and tail call elimination.
However, you still can optimize further by eliminating the use of redundant intermediate collections.
A. Change (redundant) list [2..limit]
to range expression 2..limit
in divisorsSum
:
for i in 2..limit do
if n%i=0 then sum <- sum+i+n/i
B. Generate array of abundants without creating the big list (more faithful to C# version):
let abundants =
let arr = ResizeArray(28123)
for i in 1..28123 do
if isAbundant i then arr.Add i
arr.ToArray()
C. Calculate solve
without creating the big list:
let solve =
loopUntil 0 0
let mutable sum = 0
for i in 1..28123 do
if not <| domain.Get(i) then
sum <- sum + i
sum
The new F# version is 4x faster than the original one; it takes about 0.1 seconds to complete.
UPDATE:
Your measurement is inaccurate. First, you measured time difference between two calls of printing values. Second, EulerXXX.solve
are values; so they are pre-calculated when you compiled your program. You should declare EulerXXX.solve
as functions:
let solve() = ...
and measure execution time of function calls:
let time fn =
let sw = new System.Diagnostics.Stopwatch()
sw.Start()
let f = fn()
sw.Stop()
printfn "Time taken: %.2f s" <| (float sw.ElapsedMilliseconds)/1000.0
f
let s023 = time Euler023.solve
printf "Euler023 solution: %A" s023
来源:https://stackoverflow.com/questions/12036502/euler-23-in-c-0-2-seconds-in-f-30-5-seconds-why