Mathematical series, take for example the consecutive sequence represented here as an array:
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) };
Prints:
a0 = 0
a1 = 1
a2 = 2
...
a8 = 8
a9 = 9
1- Is there a simple way to drop just the first element i.e. a0 = 0
from the printed output?
2- Could this code be made more idiomatic?
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];
Output:
(a0 => 0)
(a1 => 1)
(a2 => 2)
(a3 => 3)
(a4 => 4)
------
(b1 => 1)
(b2 => 2)
(b3 => 3)
(b4 => 4)
(b5 => 5)
I recognize the above doesn't include your 'bespoke' gist
ellipsis conditional ( ($^k < 4 or $^k > 7)
), but you seem to have come up with a more elegant way of writing that in the comments. Still (if you don't want to use skip
), number keys yourself and include an offset such as i
or j
indicating how many elements of @seq
you wish to skip.
Addendum: Below is an attempt at implementing your 'bespoke' gist
ellipsis conditional (using 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"};
}
Output when \i = 0
:
(a0 => 0)
(a1 => 1)
(a2 => 2)
(a3 => 3)
...
(a8 => 8)
(a9 => 9)
Output when \i = 1
:
(a1 => 1)
(a2 => 2)
(a3 => 3)
(a4 => 4)
...
(a9 => 9)
(a10 => 10)
This might be a bit more idiomatic:
my @seq = 0, *+1 ... *;
say @seq[^4], @seq[7..10]
You don't need to use a lexical variable within the sequence; either Whatever
or placeholder variables can safely be used within sequences. Then you can simply select the elements of the sequence you want printed.
Which returns «(0 1 2 3)(7 8 9 10)»
You can skip the first N values on any Iterable
or Sequence
with skip
:
for (^5).skip(3) {
.say
}
# 3
# 4
If you don't specify a number, it will skip only one element.
Let's start with a very simple solution for printing a gist of a sequence. It doesn't deal with the specifics you've added to your question but it's a good starting point:
sub seq-range-gist ( @seq ) {
my @pairs = @seq.pairs;
join "\n", @pairs.head(3)».gist, '...', @pairs.tail(2)».gist
}
Unlike .kv
, which converts its invocant into the form key1, value1, key2, value2, key3, value3, ...
, i.e. 6 elements if its invocant contains 3 elements, .pairs
converts its invocant into the form key1 => value1, key2 => value2, key3 => value3, ...
.
I used .pairs
instead of .kv
partly because it meant I could just use ».gist
later on in the code to effortlessly get a nice key1 => value1
display for each element. We'll modify that below but this is a good idiomatic start.
The .head
and .tail
calls are the idiomatic way to create small lists of the first and last N elements from an invocant list (provided it's not lazy; more about that in a mo).
Given this initial solution, say seq-range-gist (0,1 ... Inf)[^10]
displays:
0 => 0
1 => 1
2 => 2
...
8 => 8
9 => 9
Next, we want to be able to "drop just the first element ... from the printed output". Unfortunately say seq-range-gist (0,1 ... Inf)[1..9]
displays:
0 => 1
1 => 2
2 => 3
...
7 => 8
8 => 9
We want the number on the left of the =>
to retain the numbering of the original sequence. To enable this we split the underlying sequence from the range that we want extracted. We add a second parameter/argument @range
, and append [@range]
to the second line of the sub:
sub seq-range-gist ( @seq, @range ) {
my @pairs = @seq.pairs[@range];
Now we can write say seq-range-gist (0,1 ... Inf), 1..9
to display:
1 => 1
2 => 2
3 => 3
...
8 => 8
9 => 9
In your question you used the format aINDEX = VALUE
rather than INDEX => VALUE
. To allow customization of the gist, we add a third &gist
routine parameter/argument and invoke that instead of the built in .gist
method:
sub seq-range-gist ( @seq, @range, :&gist ) {
my @pairs = @seq.pairs[@range];
join "\n", @pairs.head(3)».&gist, '...', @pairs.tail(2)».&gist
}
Note how the "method" invocations in the body of seq-range-gist
sub are now .&gist
, not .gist
. The syntax .&foo
invokes a sub &foo
(which is typically invoked by writing just foo
), passing the invocant on the left of the .
as a $_
argument to the sub.
Note also that I've made the &gist
parameter a named one by preceding it with a :
.
So now say seq-range-gist (0,1 ... Inf), 1..9, gist => { "a{.key} = {.value}" }
displays:
a1 = 1
a2 = 2
a3 = 3
...
a8 = 8
a9 = 9
The rest of this answer is bonus material for readers who care about polish.
say seq-range-gist (0, 1, 2, 3), ^3
displays:
0 => 0
1 => 1
2 => 2
...
1 => 1
2 => 2
Oops. And even if there were more pairs than the head and tail combined, so at least we didn't get repeated lines, it'd still be pointless using the head, ..., tail
approach to elide just one or two elements. Let's change the last statement in the sub body to eliminate these issues:
join "\n",
@pairs < $head + $tail + 3 # Of course, the 3 is a bit arbitrary
?? @pairs».&gist
!! (@pairs.head($head)».&gist, '...', @pairs.tail($tail)».&gist)
Next, it would be nice if the sub did something useful if called without a range or gist. We can mostly fix that by giving the @range
and &gist
parameters suitable defaults:
sub seq-range-gist (
@seq,
@range = @seq.is-lazy ?? ^100 !! ^@seq,
:&gist = { .gist }
) {
If @seq
is not lazy, then @range
defaults to the full range of @seq
. If @seq
is infinite (in which case it's also lazy), then the upto 100 default is fine. But what if @seq
is lazy but yields less than 100 defined values? To cover this case we append .grep: *.value.defined
to the @pairs
declaration:
my @pairs = @seq.pairs[@range].grep: *.value.defined;
Another simple improvement would be optional head and tail parameters, leading to a final polished solution:
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With