Perl 6列表理解

Sum*_*nal 9 list-comprehension perl6 raku

我是Windows 10 i7第四代笔记本电脑,内存为8 GB.

我想找出1到1000000000的数字之和,可被5整除.

我试图在perl 6 REPL中运行此代码:

($_ if $_%5==0 for 1..1000000000).sum
Run Code Online (Sandbox Code Playgroud)

代码运行45分钟,仍然没有输出.我怎么能克服它?

如何在这种情况下应用并发性?我认为上面的问题可以通过并发或任务并行来解决!

Chr*_*oph 17

我怎么能克服它?

通过选择更好的算法:

我们my \N = 1_000_000_000.你对价值感兴趣

[+] grep * %% 5, 1..N
Run Code Online (Sandbox Code Playgroud)

这是一样的

[+] map * * 5, 1..N/5
Run Code Online (Sandbox Code Playgroud)

反过来

5 * [+] 1..N/5
Run Code Online (Sandbox Code Playgroud)

Rakudo非常聪明,可以在恒定时间内对一个范围进行求和,您可以立即获得结果(几乎).

  • 几乎可以肯定,最快的方法是:`5*(N div 5)*(N div 5 + 1)/ 2` (2认同)

小智 11

它很慢,因为你试图重新设计物品.作为一种替代方案,你可以构建一个序列:(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
Run Code Online (Sandbox Code Playgroud)

这是计算问题的最快,最安全的方法,但这不是最有趣的!

你提到了并发性,所以让我们给这种方法一个旋转.我们的问题在于改变内容,因此我们需要找出一种方法,通过我们可用的核心数量来填充我们原始的数字范围,然后开始重新设计并找到每个核心的乘数.然后我们将总结每个核心中每个块中的内容,然后回到主线程并总结块的总和以获得最终答案.在代码中,看起来像这样:

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;
Run Code Online (Sandbox Code Playgroud)

batch是每个核心需要处理的块的大小,这里是单行的解释,它完成了每个位的所有工作:

我们使用序列运算符来创建序列0, batch, 2*batch, 3*batch等等N.既然我们想N成为它的一部分,我们第二次包括它:

(0, batch … N, N)
Run Code Online (Sandbox Code Playgroud)

我们现在想要的是使用该序列来创建一堆Range对象,我们想要重复使用序列中的元素,因此我们使用.rotor批处理2和后退1,然后展平子列表并用于.map创建Range对象(.map: *^..*看起来好多了,但唉,无论星星都不想在那种安排下讨好):

.rotor(2 => -1).flat.map({$^a^..$^b})
Run Code Online (Sandbox Code Playgroud)

现在是有趣的一点,我们使用该.race方法创建一个HyperSeq使用我们所有核心进行评估的方法.其:batch命名参数允许您指定每批处理的元素数量,并:degree指定要使用的线程数.我们已经批量处理了我们的数据,因此:batch我们使用了1.因为:degree我们使用了核心数量.我们为什么不告诉它批评我们的东西?因为物化是我们的敌人,我们想要在不同的线程中进行统治.告诉它批量处理会改变一个线程中的所有内容:

.race(:batch :degree(CORES))
Run Code Online (Sandbox Code Playgroud)

所以现在我们拿到了HyperSeq,我们可以映射它.每个项目都是我们的Range对象,适合批量调用.所以我们会调用.grep它,寻找可被我们想要的除数整除的数字,然后我们将调用.sum:

.map(*.grep(* %% DIV).sum)
Run Code Online (Sandbox Code Playgroud)

最后一个樱桃,我们想要总结每个块的总和并打印结果,所以我们打电话

.sum.say;
Run Code Online (Sandbox Code Playgroud)

田田!

您也可以通过这种方式重新编写有趣的位,并使用Promises而不是.race:

say sum await (0, batch … N, N).rotor(2 => -1).flat.map:
    { start sum grep * %% DIV, $^a^..$^b }
Run Code Online (Sandbox Code Playgroud)

相似而且有点短.以前用于为我们制作Ranges的地图现在也会启动一个Promise(使用start关键字),它会对块进行greps和求和.在行的开头,我们添加await等待所有承诺的结果,然后总结结果.

它仍然很慢,并没有帮助你原来的问题,因此你应该使用一个好的算法;)

干杯.


Eli*_*sen 7

低于1_000_000_000,比如1_000_000需要多长时间?在我的机器上大约需要3秒钟.从中推断,要检查的值是1000倍,这意味着需要3000秒.

为了加快速度,可以使用%%(可被整除)运算符,而不是%(modulo):

($_ if $_ %% 5 for 1..1000000000).sum
Run Code Online (Sandbox Code Playgroud)

FWIW,我预计这会更快.我会考虑更快地制作%%,但我担心你仍然会查看算法的小时刻度.

  • FWIW,我刚刚为Int %% Int(快14倍)和Int%Int(快8倍)提供加速,前提是整数值适合本机(通常是64位)整数. (6认同)