我是在Windows 10 i7第4代笔记本电脑8 GB内存。
我想找出从1到1000000000的数字之和,可除以5。
我试图在Raku REPL中运行这段代码:
($_ if $_%5==0 for 1..1000000000).sum
代码运行了45分钟,仍然没有输出。我怎样才能克服它?
在这种情况下,如何应用并发呢?我认为上面的问题可以通过并发或任务并行来解决!!
发布于 2017-02-22 18:34:04
这很慢,因为你想要还原一组物品。作为另一种选择,您可以构造一个序列:(5, 10 … 1000000000).sum
,但这仍然在大量元素周围进行整合和保持,所以它仍然很慢。您可以做一个C风格的循环,并为每个增量添加和,但这并不有趣(对于足够大的数字,这仍然缓慢)。
你可以用数学来解决这个问题:可以被N整除的数字是它的倍数,如果我们把N从这个序列中剔除,我们会发现你要寻找的所有数字的和都是N * (1 + 2 + ... + floor 1000000000/N)
。因为这是一个连续的范围在那里,我们可以使用它的Range.sum
(它知道如何快速完成)来计算该部分。因此,我们得到:
sub sum-of-stuff (\n, \d) { d * sum 1..n div d; }
say sum-of-stuff 1000000000, 5 # OUTPUT: 100000000500000000
因此,这是最快和最明智的方法来计算你的问题,但这不是最有趣的!
您提到了并发性,所以让我们来讨论一下这个方法。我们的问题是将物质重新化,所以我们需要想出一种方法,用我们现有的核数来把我们原来的数字范围分块,然后再对每个单独的核心进行放大和寻找乘法器的工作。然后我们将总结每个核心中每个块中的内容,然后回到主线程,总结各块的总和,以得到最终的答案。在代码中,应该是这样的:
constant N = 10000;
constant DIV = 5;
constant CORES = 32;
constant batch = N div CORES;
(0, batch … N, N).rotor(2 => -1).flat.map({$^a^..$^b})
.race(:batch :degree(CORES)).map(*.grep(* %% DIV).sum).sum.say;
batch
是每个核心需要处理的块的大小,下面是为每一位完成所有工作的一行代码的解释:
我们使用序列操作符来创建一个0, batch, 2*batch, 3*batch
序列,等等,直到N
。由于我们希望N
成为它的一部分,所以我们第二次将它包括在内:
(0, batch … N, N)
我们现在想要的是使用该序列来创建一组Range
对象,我们希望重用序列中的元素,所以我们使用批处理2和后退1的.rotor
,然后扁平子列表并使用.map
来创建范围对象(.map: *^..*
看起来好多了,但是遗憾的是,无论星体不想在这种安排中得到什么):
.rotor(2 => -1).flat.map({$^a^..$^b})
现在是有趣的地方,我们使用.race
方法来创建一个HyperSeq
,以便使用我们所有的核心来评估它。它的:batch
命名参数允许您指定每个批处理多少个元素,它的:degree
指定要使用多少个线程。我们已经对数据进行了批量处理,因此对于:batch
,我们使用了1
。对于:degree
,我们使用我们的核数。为什么我们不让它把我们的东西分批?因为具体化是我们的敌人,我们想要在不同的线上具体化。让它分批处理会使所有的内容在一个线程中具体化,而不是:
.race(:batch :degree(CORES))
所以现在我们已经掌握了我们的HyperSeq,我们可以在上面绘制地图。每个项目都是我们的Range
对象大小为一批,召回。所以我们在上面调用.grep
,寻找可以被我们想要的除数整除的数字,然后调用.sum
.map(*.grep(* %% DIV).sum)
在顶部的最后一个樱桃,我们想要总结每个块的和,并打印结果,所以我们调用
.sum.say;
塔达!
您也可以用这种方式重写有趣的部分,并使用承诺而不是.race
。
say sum await (0, batch … N, N).rotor(2 => -1).flat.map:
{ start sum grep * %% DIV, $^a^..$^b }
很相似,也有点短。以前为我们创建Range的映射现在也激发了一个承诺(使用start
关键字),它对块进行打招呼和求和。在行的开头,我们添加了await
,等待所有承诺的结果,然后总结结果。
这仍然是缓慢的地狱,不会帮助你原来的问题,因此,为什么你应该使用一个好的阿尔戈;)
干杯。
发布于 2017-02-22 18:07:42
我怎样才能克服它?
通过选择更好的算法:
让my \N = 1_000_000_000
。你对这个价值感兴趣
[+] grep * %% 5, 1..N
这和
[+] map * * 5, 1..N/5
这反过来
5 * [+] 1..N/5
Rakudo足够聪明,可以在固定时间内将一个范围相加,您将立即得到结果(几乎)。
发布于 2017-02-22 16:39:43
1_000_000_000说,这需要多长时间才能得到比1_000_000低的值?在我的机器上大约需要3秒。由此推断,1000倍的值需要检查,这意味着大约需要3000秒。
为了使事情更快,您可以使用%% (可以被)运算符,而不是%(模块化):
($_ if $_ %% 5 for 1..1000000000).sum
我还以为这会更快呢。我会考虑使%%更快,但我担心你仍然会看你的算法的小时规模的时间。
https://stackoverflow.com/questions/42391754
复制相似问题