在Raku中简洁地打印数学序列

9

数学序列,以连续的序列为例,可以用数组表示:

my @seq = my $a=0, {++$a} ... *;
for @seq[^10].kv {state $f=0; ($^k < 4 or $^k > 7) ?? say "a$^k =  "  ~ $^v !! (say "..." if $f ne 1; $f=1) };

输出:

a0 =  0
a1 =  1
a2 =  2
...

a8 =  8
a9 =  9

1- 有没有一种简单的方法可以仅从打印输出中删除第一个元素,即a0 = 0

2- 这段代码能否更具习惯性?


@DanBron 谢谢您的评论。我刚刚编辑并详细说明了原始帖子。 - Lars Malmsteen
4个回答

8
这可能会更加习惯用语:
my @seq = 0, *+1 ... *;
say @seq[^4], @seq[7..10]

在序列内部您不需要使用词法变量;在序列中可以安全地使用Whatever占位符变量。然后,您可以简单地选择要打印的序列元素。

这将返回«(0 1 2 3)(7 8 9 10)␤»


感谢您的回答。whatever运算符很有新意,但序列输出并没有解决主要问题。我想按照数学教科书上所见的方式打印序列,即在其中使用...符号。 - Lars Malmsteen

7

您可以使用skip方法跳过任何IterableSequence中的前N个值:

for (^5).skip(3) {
    .say
}
# 3
# 4

如果您不指定数字,它将仅跳过一个元素。

“skip” 似乎只是删除输出,即第0个索引(a0)的元素仍然存在。我尝试过使用“@seq:delete”,但它只是用“(Any)”替换了第0个元素。 - Lars Malmsteen
确实。skip将只是像跳过的元素不存在一样。这可能是你想要的,也可能不是 :-) - Elizabeth Mattijsen
1
也许 for @seq[^10].kv.skip(2) 是你正在寻找的? - Elizabeth Mattijsen
1
对于第二个问题:与其使用 ($^k < 4 or $^k > 7),不如使用 ($^k == (4..7).none),虽然这种写法不太简洁,但更符合惯用语。 - Lars Malmsteen
考虑到在这种情况下$^k是一个整数值,那么你可以认为它是等价的。我不会说这一定更符合惯用法:你不必一直使用Raku的所有可能特性 :-) - Elizabeth Mattijsen
显示剩余2条评论

3

一个简单的解决方案

让我们从一个非常简单的解决方案开始,用于打印序列的要点。它不涉及您在问题中添加的具体细节,但这是一个很好的起点:

sub seq-range-gist ( @seq ) {
  my @pairs = @seq.pairs;
  join "\n", @pairs.head(3)».gist, '...', @pairs.tail(2)».gist
}

.kv 不同,它将其调用方转换为形式 key1, value1, key2, value2, key3, value3, ...,即如果其调用方包含 3 个元素,则有 6 个元素;.pairs 将其调用方转换为形式 key1 => value1, key2 => value2, key3 => value3, ...

我使用 .pairs 而不是 .kv 部分原因是因为这意味着我可以在代码后面轻松地使用 ».gist 来得到每个元素的好看的 key1 => value1 显示。我们将在下面修改它,但这是一个很好的惯用方法。

.head.tail 调用是从调用方列表中创建前 N 个元素和后 N 个元素的小列表的惯用方式(前提是它不是懒惰的;更多关于这个的内容稍后会详细说明)。

给定此初始解决方案,say seq-range-gist (0,1 ... Inf)[^10] 显示:

0 => 0
1 => 1
2 => 2
...
8 => 8
9 => 9

接下来,我们希望能够“仅从打印输出中删除第一个元素...”。不幸的是,say seq-range-gist (0,1 ... Inf)[1..9] 显示:
0 => 1
1 => 2
2 => 3
...
7 => 8
8 => 9

我们希望在 => 左侧的数字保留原始序列的编号。为了实现这一点,我们将基础序列从我们想要提取的范围中分离出来。我们添加第二个参数/参数@range,并将 [@range] 追加到子程序的第二行。
sub seq-range-gist ( @seq, @range ) {
  my @pairs = @seq.pairs[@range];

现在我们可以写 say seq-range-gist (0,1 ... Inf), 1..9 来显示:
1 => 1
2 => 2
3 => 3
...
8 => 8
9 => 9

在您的问题中,您使用了格式 aINDEX = VALUE 而不是INDEX => VALUE。为了允许对Gist进行自定义,我们添加了第三个&gist例程参数/参数,并调用该参数而不是内置的. gist 方法:
sub seq-range-gist ( @seq, @range, :&gist ) {
  my @pairs = @seq.pairs[@range];
  join "\n", @pairs.head(3)».&gist, '...', @pairs.tail(2)».&gist
}

请注意,seq-range-gist 子程序体中的 "method" 调用现在是 .&gist,而不是 .gist。语法 .&foo 调用了一个 sub &foo(通常通过编写 foo 来调用),并将左侧的 invocant 作为 $_ 参数传递给 sub。
请注意,我也通过在 &gist 参数前加上一个 :,使其成为一个命名参数。
因此,现在 say seq-range-gist (0,1 ... Inf), 1..9, gist => { "a{.key} = {.value}" } 显示:
a1 =  1
a2 =  2
a3 =  3
...
a8 =  8
a9 =  9

增加光泽

对于关心光泽的读者,本答案的其余部分是额外材料。

say seq-range-gist (0, 1, 2, 3), ^3 显示:

0 => 0
1 => 1
2 => 2
...
1 => 1
2 => 2

糟糕。即使有更多的配对比头和尾部组合还要多,这样至少我们不会得到重复的行,但是使用“head,...,tail”方法来省略一个或两个元素仍然是没有意义的。让我们改变子体中的最后一条语句以消除这些问题:

  join "\n",
    @pairs < $head + $tail + 3   # Of course, the 3 is a bit arbitrary
      ?? @pairs».&gist
      !! (@pairs.head($head)».&gist, '...', @pairs.tail($tail)».&gist)

接下来,如果在没有范围或要点的情况下调用子程序时,它能够执行一些有用的操作会更好。我们可以通过为@range&gist参数提供适当的默认值来解决这个问题:

sub seq-range-gist (
  @seq,
  @range = @seq.is-lazy ?? ^100 !! ^@seq,
  :&gist = { .gist }
) {

如果@seq不是懒惰的话,@range默认为@seq的完整范围。如果@seq是无限的(在这种情况下它也是懒惰的),那么默认的最多100个就可以了。但是如果@seq是懒惰的,但产生的定义值少于100个怎么办?为了涵盖这种情况,我们将.grep:*.value.defined附加到@pairs声明中:
  my @pairs = @seq.pairs[@range].grep: *.value.defined;

另一个简单的改进是可选的头部和尾部参数,从而实现最终完美的解决方案:
sub seq-range-gist (
  @seq,
  @range = @seq.is-lazy ?? ^100 !! ^@seq,
  :$head = 3,
  :$tail = 2,
  :&gist = { .gist }
) {
  my @pairs = @seq.pairs[@range].grep: *.value.defined;
  join "\n",
    @pairs <= $head + $tail + 2
      ?? @pairs».&gist
      !! (@pairs.head($head)».&gist, '...', @pairs.tail($tail)».&gist)
}

1
最小化的解决方案运行得非常好,而且也相当符合惯用语。在我的解决方案中,我不得不使用一个“标志”变量来处理 ... 部分,使其看起来更像 C 程序。因此,这实际上回答了我的问题的两个部分。至于“全面”的解决方案,它看起来有点令人生畏。 - Lars Malmsteen
1
感谢您的反馈并接受我的答案@LarsMalmsteen。话虽如此,我已经完全重写了我的答案,并且觉得它更好了。我放弃了“全面”的解决方案——我已经偏离了主题!——但我也完全重写了“最小解决方案”和相关的解释。我这样做主要是为了其他后来的读者,但您可能会从阅读新答案中获得一些价值。 - raiph

2
my @seq = my $a=0, {++$a} ... *;

my \i = 0;
say( 'a' ~ (i+$_) Z=> (i+$_) ) for @seq[^5]; 

print "------\n";

my \j = 1;
say( 'a'.succ ~ (j+$_) Z=> (j+$_) ) for @seq[^5]; 

输出:

(a0 => 0)
(a1 => 1)
(a2 => 2)
(a3 => 3)
(a4 => 4)
------
(b1 => 1)
(b2 => 2)
(b3 => 3)
(b4 => 4)
(b5 => 5)

我知道上面的内容并不包括您定制的 gist 省略条件(($^k < 4 or $^k > 7)),但是您似乎在评论中想出了一种更优雅的写法。如果您不想使用 skip,请自己编号,并包括一个偏移量,如 ij,表示您希望跳过 @seq 的多少个元素。
补充:下面是尝试实现您的 'bespoke' gist 省略条件(使用 grep)的代码:
my @seq = my $a=0, {++$a} ... *;
my \i = 0; my \m = 4; my \n = 7;

do for @seq[^10].grep({4 > $_ or $_ > 7 }) {
  say 'a' ~ (i+$_) Z=> (i+$_); 
  if  $_ == 3 {print "...\n"};
}

\i = 0 时的输出结果:

(a0 => 0)
(a1 => 1)
(a2 => 2)
(a3 => 3)
...
(a8 => 8)
(a9 => 9)

\i = 1时的输出结果:

(a1 => 1)
(a2 => 2)
(a3 => 3)
(a4 => 4)
...
(a9 => 9)
(a10 => 10)

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