Assign a Seq(Seq) into arrays
TL;DR Binding is faster than assignment, so perhaps this is the best practice solution to your problem:
:(@ra1, @ra2) := <10 20>.map(...);
While uglier than the solution in the accepted answer, this is algorithmically faster because binding is O(1)
in contrast to assignment's O(N)
in the length of the list(s) being bound.
Assigning / copying
Simplifying, your non-working code is:
(@listvar1, @listvar2) = list1, list2;
In Raku infix =
means assignment / copying from the right of the =
into one or more of the container variables on the left of the =
.
If a variable on the left is bound to a Scalar
container, then it will assign one of the values on the right. Then the assignment process starts over with the next container variable on the left and the next value on the right.
If a variable on the left is bound to an Array
container, then it uses up all remaining values on the right. So your first array variable receives both list1
and list2
. This is not what you want.
Simplifying, here's Christoph's answer:
@listvar1, @listvar2 Z= list1, list2;
Putting the =
aside for a moment, Z
is an infix version of the zip
routine. It's like (a physical zip pairing up consecutive arguments on its left and right. When used with an operator it applies that operator to the pair. So you can read the above Z=
as:
@listvar1 = list1;
@listvar2 = list2;
Job done?
Assignment into Array
containers entails:
Individually copying as many individual items as there are in each list into the containers. (In the code in your example
list1
andlist2
contain 5 elements each, so there would be 10 copying operations in total.)Forcing the containers to resize as necessary to accommodate the items.
Doubling up the memory used by the items (the original list elements and the duplicates copied into the
Array
elements).Checking that the type of each item matches the element type constraint.
Assignment is in general much slower and more memory intensive than binding...
Binding
:(@listvar1, @listvar2) := list1, list2;
The :=
operator binds whatever's on its left to the arguments on its right.
If there's a single variable on the left then things are especially simple. After binding, the variable now refers precisely to what's on the right. (This is especially simple and fast -- a quick type check and it's done.)
But that's not so in our case.
Binding also accepts a standalone signature literal on its left. The :(...)
in my answer is a standalone Signature
literal.
(Signatures are typically attached to a routine without the colon prefix. For example, in sub foo (@var1, @var2) {}
the (@var1, @var2)
part is a signature attached to the routine foo
. But as you can see, one can write a signature separately and let Raku know it's a signature by prefixing a pair of parens with a colon. A key difference is that any variables listed in the signature must have already been declared.)
When there's a signature literal on the left then binding happens according to the same logic as binding arguments in routine calls to a receiving routine's signature.
So the net result is that the variables get the values they'd have inside this sub:
sub foo (@listvar1, @listvar2) { }
foo list1, list2;
which is to say the effect is the same as:
@listvar1 := list1;
@listvar2 := list2;
Again, as with Christoph's answer, job done.
But this way we'll have avoided assignment overhead.
Not entirely sure if it's by design, but what seems to happen is that both of your sequences are getting stored into @ra1
, while @ra2
remains empty. This violates the type constraint.
What does work is
@ra1, @ra2 Z= <10 20>.map(...);