I've come across this code at rosettacode
my @pascal = [1], { [0, |$_ Z+ |$_, 0] } ... Inf;
.say for @pascal[^4];
# ==>
# [1]
# [1 1]
# [1 2 1]
# [1 3 3 1]
Inside the explicit generator block, I know how the individual operators like the list flattening |
and the zip operator Z+
work but I have difficulty comprehending how they cooperate to generate the next array. Can somebody explain in detail how it works? Thank you.
Note: The code is slightly rearranged for brevity, i.e. it's superficially different than the one in Rosetta.
So let's take a look at what's going on.
First up the Sequence generator ...
this takes a list of starting values, a code block itself and and end point.
It uses the starting values to generate each next item in the list so lets start with @pascal[0]
that's simple enough : [1]
For @pascal[1]
we call the code block with our value in @pascal[0]
like so :
sub ( $array ) {
[0, |$array Z+ |$array, 0]
}
You'll note I've made it into a sub, this is just so I can explain things easier. In the anonymous code block $_ is the incoming data and I've called it $array
. So what code to we run when $array == [1]
?
[0, |[1] Z+ |[1], 0] => [1,1]
So here's a question what happens if we don't use the |
?
[0, [1] Z+ [1], 0] => [1,1]
It's the same! So whys it there? Well what happens if we set $array == [3]
?
[0, [3] Z+ [3], 0] => [1,1]
Weird? No, because the Z+
transforms like this :
[0, [3] Z+ [3], 0] => [0 + [3], [3] + 0] => [1,1]
Z
makes a new list by zipping the elements with the given operator +
between them. +
is doing a numerical computation and the Numerical representation of an Array is the number of elements in it, in this case 1 both times.
And this is where the |
slip operator comes in, it slips the array it's given open merging into the list context it's in. So lets go back to @pascal[1]
[0, |[1] Z+ |[1], 0] => [0, 1 Z+ 1, 0] => [0 + 1, 1 + 0] => [1,1]
Ok.. So @pascal[2]
is calling the same block but now passing in [1,1]
[0, |[1, 1] Z+ |[1, 1], 0] => [0, 1, 1 Z+ 1, 1, 0] => [0 + 1, 1 + 1, 1 + 0] => [1,2,1]
And so on into Infinity!
I hope that's helped to explain what's going on?
This is a somewhat interesting application of the sequence operator, in so far as the values that it produces each time are arrays. So:
[1]
is the first array produced at the start of the sequenceInf
will never be matched, so the sequence will go on foreverA simpler example may be helpful: the sequence [1], [1,1], [1,1,1], ...
. That is, we want to produce an array that is the previous item in the sequence with an extra 1
at the end. We could do this:
my @ones = [1], { [|$_, 1] } ... Inf;
.say for @ones[^4];
Producing:
[1]
[1 1]
[1 1 1]
[1 1 1 1]
The first time the block is called, it gets [1]
in $_
. We slip that with |
, thus producing an array [1, 1]
. That is passed to the block the second time, and so forth.
So, breaking down [0, |$_ Z+ |$_, 0]
. The outer square brackets are an array composer. The rest can be read as 0, |$_
zipped with |$_, 0
, using the +
operator to zip the things.
The first call to be block will pass [1]
, thus we will be zipping 0, 1
and 1, 0
. Zipping with an operator applies it pairwise, meaning it will calculate 0 + 1, 1 + 0
. The overall result is the array [1,1]
.
The second call to the block gets that [1,1]
. It forms the lists 0, 1, 1
and 1, 1, 0
, and then the zip operator forms the pairwise addition again, which is 0 + 1, 1 + 1, 1 + 0
. The overall result is the array [1,2,1]
.
Effectively, then, the result grows an element each time, resulting from the pairwise addition of the previous result padded with a zero at either end.
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