检查 Raku 中列表的所有元素是否为质数

7
my @g = (1,2,3,4);
say reduce {is-prime}, @g; # ==> gives error
say reduce {is-prime *}, @g; #==> gives error
say reduce {is-prime}, (1,2,3,4); # ==> gives error
say so is-prime @g.all; # ==> gives error

如何在 Raku 中检查列表中的所有元素是否为质数?

1
我不会因为 so is-prime @g.all 而出现错误,尽管对我来说将其写作 so is-prime all @g?@g.all.is-prime 更为合理。 - user0721090601
5个回答

9
上面的答案都很有帮助,但它们没有解释为什么你的解决方案不起作用。基本上reduce不会对列表的每个成员应用一个函数(在您的情况下是is-prime)。你需要使用map。错误信息如下:
Calling is-prime() will never work with signature of the proto ($, *%)

因为reduce期望一个中缀、二元函数或者一个有两个参数的函数;它所做的是将它们应用于第一对元素,然后应用于结果和第三个元素,以此类推。最后一个语句由于相似的原因不起作用:你正在使用列表参数而不是单个参数调用is-prime函数。

1
这是最有帮助的答案,因为它解释了为什么我的尝试没有成功。我形成了自己的解决方案,模仿 say reduce { $^a + $^b }, (1,2,3); 现在我们知道为什么它不起作用,因为 reduce 需要一个带有两个参数的函数,但 is-prime 只能操作一个参数。 - Lars Malmsteen

7
你基本上是在问:这个列表中是否有任何非质数元素?我会将其写为:
say "not all prime" if @g.first: !*.is-prime;

请注意,根据`is-prime`函数,明显的数字1被认为不是质数:
say 1.is-prime;  # False

因此,first 会在你的示例中的 1 上触发,而不是在 4 上触发。

使用lambda表达式迭代列表中的“first”是一个好主意。 - Lars Malmsteen
1
那应该真正使用 with 而不是 if。如果第一个值是 0,会怎样呢? - Brad Gilbert
即使在这种情况下使用 with,也无法捕获实际元素是类型对象的情况,例如 (Int,Str,Date).first: { $_ ~~ Str }。因此,您需要查看实际使用情况。由于我们不检查 0 是否为质数,所以 if 应该可以胜任 :-) - Elizabeth Mattijsen
这就是为什么 grepfirst 更适合这种情况的原因。 - Brad Gilbert
1
并不完全如此,因为grep将始终测试所有元素,它不会短路。您可以通过使用:k副词和with来始终使用firstsay "found" with (Int,Str,Date).first(Int, :k); # found - Elizabeth Mattijsen

5

这个存在一个大问题:

say reduce {is-prime}, @g;

您创建了一个Lambda函数:

{  }

它唯一要做的事情就是调用一个函数:
is-prime

虽然你没有给函数传递任何参数。
但它难道不应该猜测参数应该是什么吗?

如果您想把is-prime作为引用传递,那么您应该使用&is-prime而不是{is-prime}。 当然这仍然不会起作用。

另一个问题是reduce通过递归组合值来操作。
如果一次只操作一个参数,则无法执行此操作。
裸块lambda {}需要零个或一个参数,而不是两个或多个。


reduce经常与map结合使用。

这种情况发生得如此频繁,以至于维基百科上有一个MapReduce页面。

say ( map &is-prime, @g ==> reduce { $^a and $^b } );
# False

say ( map &is-prime, 2,3,5 ==> reduce { $^a and $^b } );
# True

我是这样写的,以便在reduce之前一行中含有map,但也许这样更清晰:

say reduce {$^a and $^b}, map &is-prime, 2,3,5;
# True

使用中缀运算符的reduce非常普遍,因此有一种更短的编写方式。

say [and] map &is-prime, 2,3,5;
# True

当然,最好找到第一个不是质数的值,并说出相反的结果。

因为如果有任何一个值不是质数,那就意味着它们全部都不是质数。

但是你必须小心,因为你可能认为这样的方法总是有效:

not @g.first: !*.is-prime;

它在你给定的值上确实可以工作,但并非总是如此。
first 如果找不到该值,则返回 Nil

not (2,3,5).first: !*.is-prime;
# not Nil === True

not (2,3,4).first: !*.is-prime;
# not 4   === False

not (2,3,0,4).first: !*.is-prime;
# not 0   === True

那最后一个返回了0,当与not组合时返回True。您可以使用defined来解决这个问题。
not defined (2,3,0,4).first: !*.is-prime;
# False

这仅在first不会返回一个恰好在列表中的未定义元素时才有效。
(Int,Any).first: Real
# Int

defined (Int,Any).first: Real
# False

你可以通过请求索引而不是值来解决这个问题。
当然,你仍然需要使用defined
(Int,Any).first: :k, Real
# 0

defined (Int,Any).first: :k, Real
# True

另一种修复方法是仅使用 grep
not (2,3,0,4).grep: !*.is-prime;
# not (0,4) === False

grep总是返回一个List,因此您无需担心检查0或未定义元素。
(如果List包含任何元素,则为True,而不管元素的值如何。)

grep足够聪明,知道如果将其强制转换为Bool,它可以在找到第一个值时停止。 因此,它使用与使用first相同的短路技术。


这会导致一些相当奇怪的代码,有那两个否定运算符。因此,它应该放入一个函数中。

sub all-prime ( +@_ ) {
  # return False if we find any non-prime
  not @_.grep: !*.is-prime
  # grep short-circuits in Bool context, so this will stop early
}

如果您提供了一些奇怪的东西,这仍然可能会失败。

all-prime 2,3,5, Date.today;
# ERROR: No such method 'is-prime' for invocant of type 'Date'

如果您关心的话,请添加一些错误处理。

sub all-prime ( +@_ ) {
  # return Nil if there was an error
  CATCH { default { return Nil }}

  # return False if we find any non-prime
  not @_.grep: !*.is-prime
}

all-prime 2,3,5, Date.today;
# Nil

5

当然有很多种方法可以实现这个功能。一种非常明确的方式是使用for循环:

for @g -> $g {
 if $g.is-prime {
  say $g;
 }
}

或者使用grep命令(可以隐式地保留$_):

@g.grep({ $_.is-prime }).say

以上两种方法都假设您真的想要过滤掉质数。当然,您也可以真正“检查”每个数字并获得布尔值:

@g.map({ .is-prime }).say

1
谢谢您的回答。不过我只是想得到一个最终结果,即 TrueFalse。这些示例仍然很有帮助,因为它们展示了如何正确地迭代列表。 - Lars Malmsteen
FYI。一个没有括号的表达方式是 say @g .grep: *.is-prime。在我的第一个 SO 的答案中提到的 where 子句中,*+* 中的相同。在这种情况下,你会得到(相同的) { $_.is-prime }.grep: ....grep(...) 的替代方法。如果你想/需要链式调用方法,就不能使用 .foo: ...,但如果不需要,它通常很好用。 - raiph

3

使用all连接符:

say so all @g».is-prime; # False

1
使用超算符是一个好主意,因为它可以使代码更短,并且使用all函数(这里称为连接器还是方法?)来评估结果列表也是一个好主意。这真的很有帮助。 - Lars Malmsteen
1
这确实有效。但是在 so 可以在遇到第一个 False 特征态之前,它将创建一个带有 所有 特征态的 Junction。由于 .is-prime 可能非常昂贵,需要首先完成所有这些计算的事实可能会使这种方法的速度与 .first 解决方案相当,仅当最后一个特征态为 False 时。在所有其他情况下,first 方法可能会快几个数量级。 - Elizabeth Mattijsen
ElizabethMittijsen: 如果改成 @g.all.is-prime,那么这个操作不就会转移到可短路的连接处吗?(我很惊讶没有人建议使用 @g.all.is-prime,因为对我个人来说这是最易读的) - user0721090601
@user0721090601 我认为(希望)你是对的。当然,即使在您的公式中短路计算是(希望)算法上相同的,交叉口仍然会变慢,因为交叉口本来就比较慢。在一个有点相关的注释中,但是考虑到未来的希望,短路评估可以取消不需要的并行计算,实际上发生在并行计算中,我真的很希望您能阅读我的这条评论,并告诉我是否理解了我的意图或者需要澄清。谢谢。 - raiph
1
短路发生在 so 中,技术上是在 Junction.Bool 中。为此,必须构建整个 Junction - Elizabeth Mattijsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接