在 Raku 中简洁地打印数学系列
Printing mathematical series concisely in Raku
数学级数,以这里表示为数组的连续数列为例:
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- 这段代码可以更地道吗?
您可以使用 skip
:
跳过 any Iterable
或 Sequence
上的前 N 个值
for (^5).skip(3) {
.say
}
# 3
# 4
如果您不指定数字,它将只跳过一个元素。
这可能有点地道:
my @seq = 0, *+1 ... *;
say @seq[^4], @seq[7..10]
您不需要在序列中使用词法变量; Whatever
or placeholder variables 可以安全地在序列中使用。然后你可以简单地 select 你想要打印的序列的元素。
其中 returns «(0 1 2 3)(7 8 9 10)»
准系统解决方案
让我们从打印序列要点的非常简单的解决方案开始。它不涉及您添加到问题中的具体细节,但它是一个很好的起点:
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
将其调用者转换为[=25=形式].
我使用 .pairs
而不是 .kv
的部分原因是这意味着我可以稍后在代码中使用 ».gist
来毫不费力地为每个对象获得漂亮的 key1 => value1
显示元素。我们将在下面对其进行修改,但这是一个很好的惯用开头。
.head
和 .tail
调用是从调用列表中创建第一个和最后一个 N 元素的小列表的惯用方法(前提是它不是懒惰的;更多关于 mo 的内容) .
鉴于此初始解决方案,say seq-range-gist (0,1 ... Inf)[^10]
显示:
0 => 0
1 => 1
2 => 2
...
8 => 8
9 => 9
接下来,我们希望能够"drop just the first element ... from the printed output"。不幸的是 say seq-range-gist (0,1 ... Inf)[1..9]
显示:
0 => 1
1 => 2
2 => 3
...
7 => 8
8 => 9
我们希望=>
左边的数字保留原来序列的编号。为了实现这一点,我们将基础序列从我们想要提取的范围中分离出来。我们添加第二个 parameter/argument @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
例程 parameter/argument 并调用它而不是内置的 .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
来调用),传递 [=] 左侧的调用者48=] 作为 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
添加润色
此回答的其余部分是对关心润色的读者的奖励material。
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)
接下来,如果 sub 在没有范围或要点的情况下调用时做了一些有用的事情,那就太好了。我们主要可以通过给 @range
和 &gist
参数适当的默认值来解决这个问题:
sub seq-range-gist (
@seq,
@range = @seq.is-lazy ?? ^100 !! ^@seq,
:&gist = { .gist }
) {
如果@seq
不是而不是lazy,则@range
默认为@seq
的全范围。如果 @seq
是 infinite (在这种情况下它也是惰性的),那么 upto 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)
}
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)
我知道上面不包括你的 'bespoke' gist
省略号条件( ($^k < 4 or $^k > 7)
),但你似乎想出了一种更优雅的写作方式评论。仍然(如果您不想使用 skip
),您自己使用数字键并包含一个偏移量,例如 i
或 j
,指示您希望 @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)
数学级数,以这里表示为数组的连续数列为例:
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- 这段代码可以更地道吗?
您可以使用 skip
:
Iterable
或 Sequence
上的前 N 个值
for (^5).skip(3) {
.say
}
# 3
# 4
如果您不指定数字,它将只跳过一个元素。
这可能有点地道:
my @seq = 0, *+1 ... *;
say @seq[^4], @seq[7..10]
您不需要在序列中使用词法变量; Whatever
or placeholder variables 可以安全地在序列中使用。然后你可以简单地 select 你想要打印的序列的元素。
其中 returns «(0 1 2 3)(7 8 9 10)»
准系统解决方案
让我们从打印序列要点的非常简单的解决方案开始。它不涉及您添加到问题中的具体细节,但它是一个很好的起点:
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
将其调用者转换为[=25=形式].
我使用 .pairs
而不是 .kv
的部分原因是这意味着我可以稍后在代码中使用 ».gist
来毫不费力地为每个对象获得漂亮的 key1 => value1
显示元素。我们将在下面对其进行修改,但这是一个很好的惯用开头。
.head
和 .tail
调用是从调用列表中创建第一个和最后一个 N 元素的小列表的惯用方法(前提是它不是懒惰的;更多关于 mo 的内容) .
鉴于此初始解决方案,say seq-range-gist (0,1 ... Inf)[^10]
显示:
0 => 0
1 => 1
2 => 2
...
8 => 8
9 => 9
接下来,我们希望能够"drop just the first element ... from the printed output"。不幸的是 say seq-range-gist (0,1 ... Inf)[1..9]
显示:
0 => 1
1 => 2
2 => 3
...
7 => 8
8 => 9
我们希望=>
左边的数字保留原来序列的编号。为了实现这一点,我们将基础序列从我们想要提取的范围中分离出来。我们添加第二个 parameter/argument @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
例程 parameter/argument 并调用它而不是内置的 .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
来调用),传递 [=] 左侧的调用者48=] 作为 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
添加润色
此回答的其余部分是对关心润色的读者的奖励material。
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)
接下来,如果 sub 在没有范围或要点的情况下调用时做了一些有用的事情,那就太好了。我们主要可以通过给 @range
和 &gist
参数适当的默认值来解决这个问题:
sub seq-range-gist (
@seq,
@range = @seq.is-lazy ?? ^100 !! ^@seq,
:&gist = { .gist }
) {
如果@seq
不是而不是lazy,则@range
默认为@seq
的全范围。如果 @seq
是 infinite (在这种情况下它也是惰性的),那么 upto 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)
}
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)
我知道上面不包括你的 'bespoke' gist
省略号条件( ($^k < 4 or $^k > 7)
),但你似乎想出了一种更优雅的写作方式评论。仍然(如果您不想使用 skip
),您自己使用数字键并包含一个偏移量,例如 i
或 j
,指示您希望 @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)