我正在再次解决Project Euler问题(在我学习C#之前做了23个第一个问题)而且我对我解决问题5的解决方案的表现感到非常困惑。
内容如下:
2520是可以除以每个数字的最小数字 从1到10没有任何余数。
可被所有人整除的最小正数是多少 从1到20的数字?
现在,我在C#中使用了令人难以置信的原始蛮力解决方案,在大约25秒内解决了这个问题。
var numbers = Enumerable.Range(1, 20);
int start = 1;
int result;
while (true)
{
if (numbers.All(n => start % n == 0))
{
result = start;
break;
}
start++;
}
现在我的F#解决方案也使用暴力强制,但至少它会有更多的歧视,所以它“应该”在我的脑海中运行得更快,但它在约45秒时出现,所以它的速度几乎是C#one。
let p5BruteForce =
let divisors = List.toSeq ([3..20] |> List.rev)
let isDivOneToTwenty n =
let dividesBy =
divisors |> Seq.takeWhile(fun x -> n % x = 0)
Seq.length dividesBy = Seq.length divisors
let findNum n =
let rec loop n =
match isDivOneToTwenty n with
| true -> n
| false -> loop (n + 2)
loop n
findNum 2520
P.S - 我知道我的解决方案可能会更好,在这种情况下,我只是想知道一个更好的蛮力解决方案可能比原始解决方案慢得多。
答案 0 :(得分:11)
您可以使用List.forall
代替转换为seq,然后执行Seq.length
:
let divisors = [3..20] |> List.rev
let isDivOneToTwenty n = divisors |> List.forall (fun d -> n % d = 0)
Seq.length
将需要遍历整个序列以确定元素的数量,而forall
可以在元素未通过谓词时立即返回。
您也可以将findNum
写为:
let rec findNum n = if isDivOneToTwenty n then n else findNum (n + 2)
答案 1 :(得分:2)
即使是更直接的翻译,例如
let numbers = { 1..20 }
let rec loop start =
if numbers |> Seq.forall (fun n -> start % n = 0)
then start
else loop (start + 1)
loop 1
需要一分半钟(你的C#版本在我的机器上也需要25秒)。数字序列似乎是将其更改为数组([| 1..20 |]
)并使用Array.forall
将其降至8秒的罪魁祸首。使用数组的C#版本需要20秒(使用我自己的数组专用ForAll
方法而不是Enumerable.All
需要17秒。
List.forall
,它比阵列(~5秒)更快。
答案 2 :(得分:0)
好吧,它必须是这一点
divisors |> Seq.takeWhile(fun x -> n % x = 0)
Seq.length dividesBy = Seq.length divisors
我认为你可以将它重写为一个更简单的递归函数,它与你原来的c#实现更相似。