Actually, one steam can be infinite, but it should be left-hand one for the expected results.
(0..2) +.x [0, 10] // -> [ 0, 10, 1, 11, 2, 12 ]
(Pseries(0, 1) +.x Pseq([0, 10])).asStream.nextN(6)
// -> [ 0, 10, 1, 11, 2, 12 ]
(Pseq([0, 10]) +.x Pseries(0, 1)).asStream.nextN(6)
// -> [ 0, 1, 2, 3, 4, 5 ]
The even more confusing behavior is when you apply it to a combo of an array and an infinite stream
(Pseries(0, 1) +.x [0, 10]).asStream.nextN(3)
// -> [ [ 0, 10 ], [ 0, 10 ], [ 0, 10 ] ]
([0, 10] +.x Pseries(0, 1)).asStream.nextN(3)
// -> [ [ 0, 10 ], [ 1, 11 ], [ 2, 12 ] ]
You can [P]flatten the latter for the desired resultā¦
([0, 10] +.x Pseries(0, 1)).flatten.asStream.nextN(6)
// -> [ 0, 10, 1, 11, 2, 12 ]
but thereās the confusing usability issue that a finite Pseq needs to go āon the rightā, but a (finite, obvious) array needs to āgo on the leftā . Also, in the latter case, since you have to re-flatten, itās no different than using a plain ā+ā
([0, 10] + Pseries(0, 1)).flatten.asStream.nextN(6)
// -> [ 0, 10, 1, 11, 2, 12 ]
Thereās a special implementation for cross adverbs in BinaryOpXStream
: when the right-hand side stream yields nil
, it gets reset
. (Cross adverbs are detected by Pbinop.asStream
which uses BinaryOpXStream for the latter instead of BinaryOpStream.) That implementation makes it a non-commutative operation, but then so is its finite counterpart on arrays⦠BinaryOpXStream also works more like a flatMap in Java etc. As āproofā:
(0..2) +.x [0, 10] // same result as next line
(0..2).collect({|a| a +.x [0, 10]}).flatten // -> [ 0, 10, 1, 11, 2, 12 ]
// so, "patternizing" that idea
Pflatten(1, Pcollect({|a| a +.x [0, 10]}, Pseries(0, 1))).asStream.nextN(6) // or
Pcollect({|a| a +.x [0, 10]}, Pseries(0, 1)).flatten.asStream.nextN(6) // or
Pseries(0, 1).collect({|a| a +.x [0, 10]}).flatten.asStream.nextN(6)
// -> [ 0, 10, 1, 11, 2, 12 ]
Because of the wrapping behavior of adding a single scalar to an array, thatās the same as
(0..2).collect({|a| a + [0, 10]}).flatten //-> [ 0, 10, 1, 11, 2, 12 ]; also
Pflatten(1, Pcollect({|a| a + [0, 10]}, Pseries(0, 1))).asStream.nextN(6) // etc.
But in the more general case where Pseries returned an array, thereās a difference where you flatten, or equivalently commuting the arguments of +.x
(on an array), i.e row vs column āmajor orderā, e.g.
[0, 10] +.x [0, 0.1] // -> [ 0, 0.1, 10, 10.1 ]
[0, 0.1] +.x [0, 10] // -> [ 0, 10, 0.1, 10.1 ]
(Pseries([0, 0.1], 1) +.x Pseq([0, 10])).flatten.asStream.nextN(12) // same as
Pcollect({|a| [0, 10] +.x a}, Pseries([0, 0.1], 1)).flatten.asStream.nextN(12)
// -> [ 0, 0.1, 10, 10.1, 1, 1.1, 11, 11.1, 2, 2.1, 12, 12.1 ]
(Pseries([0, 0.1], 1).flatten +.x Pseq([0, 10])).asStream.nextN(12) // same as
Pcollect({|a| a +.x [0, 10]}, Pseries([0, 0.1], 1)).flatten.asStream.nextN(12)
// -> [ 0, 10, 0.1, 10.1, 1, 11, 1.1, 11.1, 2, 12, 2.1, 12.1 ]
N.B., *.x
is mostly called an āouterā rather than ācrossā product/operation is most non-SC contexts. Actually, āKronecker productā is probably more appropriate since the result of .x
is flattened. The .t
adverb in SC gives the true (unflattened) outer product.