Split string at given positions - raku

How do i nicely/idiomatically split a string at a list of positions?
What I have:
.say for split-at( "0019ABX26002", (3, 4, 8) );
sub split-at( $s, #positions )
{
my $done = 0;
gather
{
for #positions -> $p
{
take $s.substr($done, $p - $done );
$done = $p;
}
take $s.substr( $done, * );
}
}
which is reasonable. I am puzzled by the lack of language support for this though. If "split on" is a thing, why isn't "split at" too? I think this should be a core operation. I should be able to write
.say for "0019ABX26002".split( :at(3, 4, 8) );
Or maybe I am overlooking something?
Edit: A little Benchmark of what we have so far
O------------O---------O------------O--------O-------O-------O
| | Rate | array-push | holli | raiph | simon |
O============O=========O============O========O=======O=======O
| array-push | 15907/s | -- | -59% | -100% | -91% |
| holli | 9858/s | 142% | -- | -100% | -79% |
| raiph | 72.8/s | 50185% | 20720% | -- | 4335% |
| simon | 2901/s | 1034% | 369% | -98% | -- |
O------------O---------O------------O--------O-------O-------O
Code:
use Bench;
my $s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccddddddddddddddddddddddddddddddddddddefggggggggggggggggggg";
my #p = 29, 65, 69, 105, 106, 107;
Bench.new.cmpthese(1000, {
holli => sub { my # = holli($s, #p); },
simon => sub { my # = simon($s, #p); },
raiph => sub { my # = raiph($s, #p); },
array-push => sub { my # = array-push($s, #p); },
});
#say user($s, #p);
sub simon($str, *#idxs ) {
my #rotors = #idxs.map( { state $l = 0; my $o = $_ - $l; $l = $_; $o } );
$str.comb("").rotor( |#rotors,* ).map(*.join(""));
}
sub raiph($s, #p) {
$s.split( / <?{$/.pos == any(#p)}> / )
}
sub holli( $s, #positions )
{
my $done = 0;
gather
{
for #positions -> $p
{
take $s.substr($done, $p - $done );
$done = $p;
}
take $s.substr( $done, * );
}
}
sub array-push( $s, #positions )
{
my $done = 0;
my #result;
for #positions -> $p
{
#result.push: $s.substr($done, $p - $done );
$done = $p;
}
#result.push: $s.substr( $done, * );
#result;
}

Personally I'd split it into a list, use rotor to divide the list up and join the result :
"0019ABX26002".comb().rotor(3,1,4,*).map(*.join)
If you want a split at function (using the indexes given) :
sub split-at( $str, *#idxs ) {
my #rotors = #idxs.map( { state $l = 0; my $o = $_ - $l; $l = $_; $o } );
$str.comb("").rotor( |#rotors,* ).map(*.join(""));
}
Basically if I want to do list type stuff I use a list.
I came up with another version that I really like from a functional programming sense :
sub split-at( $str, *#idxs ) {
(|#idxs, $str.codes)
==> map( { state $s = 0;my $e = $_ - $s;my $o = [$s,$e]; $s = $_; $o } )
==> map( { $str.substr(|$_) } );
}
It works out to be slightly slower than the other one.

One way:
.say for "0019ABX26002" .split: / <?{ $/.pos ∈ (3,4,8) }> /
displays:
001
9
ABX2
6002

Because each substring does not depend on the other, hyper becomes an option.
method split-at(\p) {
do hyper for (0,|p) Z (|p,self.chars) {
self.substr: .head, .tail - .head
}
}
Or in sub form:
sub split-at(\s, \p) {
do hyper for (0,|p) Z (|p,s.chars) {
s.substr: .head, .tail - .head
}
}
But the overhead involved is not worth it unless the number of elements requested is extreme — in my tests it's about ten times slower than the naïve form.

Here's the solution I would use:
my method break (Str \s: *#i where .all ~~ Int) {
gather for #i Z [\+] 0,|#i -> ($length, $start) {
take s.substr: $start, $length
}
}
say "abcdefghi".&break(2,3,4) # "ab","cde","fghi"
The gather/take lets it be lazy if you ultimately don't need to use all of them. The loop takes #i (2,3,4 in the example) and zips it up with the cascading addition reducer [\+], which would normally produce 2,5,9, but we insert a 0 to make it 0,2,5,9 to mark the starting indexes of each one. This lets the actual take be a simple substr operation.
By making it a method instead of a sub, you can use it just like you would (you could even name it split if you want, the addition of the & sigil means Raku won't be confused whether you want the built in or custom made one.
You could, even, add it directly to Str:
use MONKEY-TYPING; # enable augment
augment class Str {
multi method split (Str \s: *#i where .all ~~ Int) {
gather for #i Z [\+] 0,|#i -> ($length, $start) {
take s.substr: $start, $length
}
}
}
say "abcdefghi".split(2,3,4)
In this case, it needs to be defined as multi method since there are already various split methods. The nice thing is since none of those are defined by only Int arguments, it's easy to ensure our augmented one gets used.
That said, calling it using the sigiled version in a lexical method is definitely the better one.

Related

Why is there such a large performance difference between these two scrips that do the same thing?

This is problem36 from the Euler Project. Sum all of the numbers below a million that are palindromic in base 2 and base 10.
I'd originally tried solving it in a more functional style.
This runs in just under 6 seconds.
[1..1_000_000]
.grep( * !%% 2 )
.grep( -> $x { $x == $x.flip } )
.grep( -> $y { $y.base(2) == $y.base(2).flip } )
.sum.say
Surprisingly this took 12 seconds even though I'm only generating odd numbers and therefore
skipping the test for even.
(1,3 ... 1_000_000)
.grep( -> $x { $x == $x.flip } )
.grep( -> $y { $y.base(2) == $y.base(2).flip } )
.sum.say
This runs in about 3 seconds.
my #pals;
for (1,3 ... 1_000_000) -> $x {
next unless $x == $x.flip;
next unless $x.base(2) == $x.base(2).flip;
#pals.push($x);
}
say [+] #pals;
I also noted that there is a significant difference between using
for (1,3 ... 1_000_000) -> $x { ...
and
for [1,3 ... 1_000_000] -> $x { ...
Anyone know why the streaming versions are so much slower than the iterative one?
And, why would those two for loops be so different in performance?
The construct [...] is an array composer. It eagerly iterates the iterable found within it, and stores each value into the array. Only then do we proceed to do the iteration. That results in far more memory allocation and is less cache-friendly. By contrast, parentheses do nothing (aside from grouping, but they don't add any semantics beyond that). Thus:
[1..1_000_000]
.grep( * !%% 2 )
.grep( -> $x { $x == $x.flip } )
.grep( -> $y { $y.base(2) == $y.base(2).flip } )
.sum.say
Will allocate and set up a million element array and iterate it, while:
(1..1_000_000)
.grep( * !%% 2 )
.grep( -> $x { $x == $x.flip } )
.grep( -> $y { $y.base(2) == $y.base(2).flip } )
.sum.say
Runs rather faster, because it need not do that.
Further, the ... operator is currently far slower than the .. operator. It's not doomed to be that way forever, it's just received a lot less attention so far. Since .grep has also been decently well optimized, it turns out to be quicker to filter out the elements made by the range - for now, anyway.
Finally, using == to compare the (string) results of base and flip is not so efficient, since it parses them back into integers, when we could use eq and compare the strings:
(1 .. 1_000_000)
.grep(* !%% 2)
.grep( -> $x { $x eq $x.flip } )
.grep( -> $y { $y.base(2) eq $y.base(2).flip } )
.sum.say
If you want something that is faster, you can write your own sequence generator.
gather {
loop (my int $i = 1; $i < 1_000_000; $i += 2) {
take $i
}
}
.grep( -> $x { $x eq $x.flip } )
.grep( -> $y { $y.base(2) eq $y.base(2).flip } )
.sum.say
Which takes about 4 seconds.
Or to go even faster, you can create the Iterator object yourself.
class Odd does Iterator {
has uint $!count = 1;
method pull-one () {
if ($!count += 2) < 1_000_000 {
$!count
} else {
IterationEnd
}
}
}
Seq.new(Odd.new)
.grep( -> $x { $x == $x.flip } )
.grep( -> $y { $y.base(2) == $y.base(2).flip } )
.sum.say
Which only takes about 2 seconds.
Of course if you want to go as fast as possible, get rid of the sequence iteration entirely.
Also use native ints.
Also cache the base 10 string. (my $s = ~$x)
my int $acc = 0;
loop ( my int $x = 1; $x < 1_000_000; $x += 2) {
next unless (my $s = ~$x) eq $s.flip;
next unless $x.base(2) eq $x.base(2).flip;
$acc += $x
}
say $acc;
Which gets it down to about 0.45 seconds.
(Caching the .base(2) didn't seem to do anything.)
This is probably close to the minimum without resorting to using nqp ops directly.
I tried writing a native int bit flipper, but it made it slower. 0.5 seconds.
(I did not come up with this algorithm, I only adapted it to Raku. I also added the +> $in.msb to fit this problem.)
I would guess that spesh is leaving in operations that don't need to be there.
Or maybe it isn't JITting very well.
It might be more performant for values larger than 1_000_000.
(.base(2).flip is O(log n) whereas this is O(1).)
sub flip-bits ( int $in --> int ) {
my int $n =
((($in +& (my int $ = 0xaaaaaaaa)) +> 1) +| (($in +& (my int $ = 0x55555555)) +< 1));
$n = ((($n +& (my int $ = 0xcccccccc)) +> 2) +| (($n +& (my int $ = 0x33333333)) +< 2));
$n = ((($n +& (my int $ = 0xf0f0f0f0)) +> 4) +| (($n +& (my int $ = 0x0f0f0f0f)) +< 4));
$n = ((($n +& (my int $ = 0xff00ff00)) +> 8) +| (($n +& (my int $ = 0x00ff00ff)) +< 8));
((($n +> 16) +| ($n+< 16)) +> (32 - 1 - $in.msb)) +& (my int $ = 0xffffffff);
}
…
# next unless (my $s = ~$x) eq $s.flip;
next unless $x == flip-bits($x);
You can even try to use multiple threads.
Note that this workload is entirely too little for this to be effective.
The overhead of using threads swamps out any benefit.
my atomicint $total = 0;
sub process ( int $s, int $e ) {
# these are so the block lambda works properly
# (works around what I think is a bug)
my int $ = $s;
my int $ = $e;
start {
my int $acc = 0;
loop ( my int $x = $s; $x < $e; $x += 2) {
next unless (my $s = ~$x) eq $s.flip;
next unless $x.base(2) eq $x.base(2).flip;
$acc += $x;
}
$total ⚛+= $acc;
}
}
my int $cores = (Kernel.cpu-cores * 2.2).Int;
my int $per = 1_000_000 div $cores;
++$per if $per * $cores < 1_000_000;
my #promises;
my int $start = 1;
for ^$cores {
my int $end = $start + $per - 2;
$end = 1_000_000 if $end > 1_000_000;
push #promises, process $start, $end;
#say $start, "\t", $end;
$start = $end + 2;
}
await #promises;
say $total;
Which runs in about 0.63 seconds.
(I messed with the 2.2 value to find a near minimum time on my computer.)

Use of uninitialized value of type Any in numeric context raku

I've come across a programming question at reddit (Take a look at the link for the question)
This was one the solutions in Python:
s="112213"
k=2
result=0
for i in range(len(s)):
num_seen = 0
window = {}
for ind in range(i, len(s)):
if not s[ind] in window:
num_seen += 1
window[s[ind]] = 1
else:
window[s[ind]] += 1
if window[s[ind]] == k:
num_seen -= 1
if num_seen == 0:
result +=1
elif window[s[ind]] > k:
break
print(result)
I've tried to port this solution into Raku and here is my code:
my #s=<1 1 2 2 1 3>;
my $k=2;
my $res=0;
for ^#s {
my $seen = 0;
my %window;
for #s[$_..*] {
if $^a == %window.keys.none {
$seen++;
%window{$^a} = 1;}
else {
%window{$^a} += 1;}
if %window{$^a} == $k {
$seen--;
if $seen == 0 {
$res++;} }
elsif %window{$^a} > $k {
last;}}}
say $res;
It gives this error:
Use of an uninitialized value of type Any in a numeric context in a block at ... line 13
How to fix it?
I don't feel that's a MRE. There are too many issues with it for me to get in to. What I did instead is start from the original Python and translated that. I'll add some comments:
my \s="112213" .comb; # .comb to simulate Python string[n] indexing.
my \k=2;
my $result=0; # result is mutated so give it a sigil
for ^s -> \i { # don't use $^foo vars with for loops
my $num_seen = 0;
my \window = {}
for i..s-1 -> \ind {
if s[ind] == window.keys.none { # usefully indent code!
$num_seen += 1;
window{s[ind]} = 1
} else {
window{s[ind]} += 1
}
if window{s[ind]} == k {
$num_seen -= 1;
if $num_seen == 0 {
$result +=1
}
} elsif window{s[ind]} > k {
last
}
}
}
print($result)
displays 4.
I'm not saying that's a good solution in Raku. It's just a relatively mechanical translation. Hopefully it's helpful.
As usual, the answer by #raiph is correct. I just want to do the minimal changes to your program that get it right. In this case, it's simply adding indices to both loops to make stuff clearer. You were using the context variable $_ in the first, and $^a in the second (inner), and it was getting unnecesarily confusing.
my #s=<1 1 2 2 1 3>;
my $k=2;
my $res=0;
for ^#s -> $i {
my $seen = 0;
my %window;
for #s[$i..*] -> $c {
if $c == %window.keys.none {
$seen++;
%window{$c} = 1;
} else {
%window{$c} += 1;
}
if %window{$c} == $k {
$seen--;
if $seen == 0 {
$res++;
}
} elsif %window{$c} > $k {
last;
}
}
}
say $res;
As you see , besides trying to indent everything a bit more properly, the only additional thing is to add -> $i and -> $c so that loops are indexed, and then use them where you were using implicit variables.

Multimethod for Proxy

Is it possible to use multidispatch for the store method when using a Proxy? In the following minimal example, the code is called when storing an Int
my $foo := do {
my $bar = 1;
Proxy.new:
:FETCH( method { return $bar} ),
:STORE( method (Int $i) { $bar = $i } )
}
say $foo; # 1
$foo = 2;
say $foo; # 2
$foo = "3"; # error, need to pass an Int
But I'd like to handle the STORE differently if given, say, a Str. The work around I've found (other than doing a mega method with given/where is to create a multi sub inside of a block, and return the sub (because a multi method can't be referred to with &foo) with an dummy first parameter:
my $foo := do {
my $bar = 1;
Proxy.new:
:FETCH( method { return $bar} ),
:STORE(
do {
multi sub xyzzy ($, Int $i) { $bar = $i }
multi sub xyzzy ($, Str $i) { $bar = +$i + 1}
&xyzzy
}
)
}
say $foo; # 1
$foo = 2;
say $foo; # 2
$foo = "3";
say $foo; # 4
Is there a better way to do this (mainly for code clarity using method because sub feels...misleading)?
With regards to being misleading: the FETCH and STORE values expecte Callables, which could be either a method or a sub.
Getting back to the question, there is no direct way of doing this, but there is a better indirect way that may be clearer. You can do this by setting up the multi sub first, and then passing the proto as the parameter:
proto sub store(|) {*}
multi sub store(\self, Int) { say "Int" }
multi sub store(\self, Str) { say "Str" }
my $a := Proxy.new(
FETCH => -> $ { 42 },
STORE => &store,
);
say $a; # 42
$a = 42; # Int
$a = "foo"; # Str
And if you want to make the code shorter, but possibly less understandable, you can get rid of the proto (because it will be auto-generated for you) and the sub in the multi (because you can):
multi store(\self, Int) { say "Int" }
multi store(\self, Str) { say "Str" }
my $a := Proxy.new(
FETCH => -> $ { 42 },
STORE => &store,
);
say $a; # 42
$a = 42; # Int
$a = "foo"; # Str

How do I return from an anonymous recursive sub in perl6?

This does what I'd expect. fib(13) returns 233.
sub fib(Int $a --> Int) {
return 0 if $a == 0;
return 1 if $a == 1;
return fib($a -1) + fib($a -2);
}
my $square = -> $x { $x * 2 }; # this works with no return value
my #list = <1 2 3 4 5 6 7 8 9>.map( $square );
# returns [2 4 6 8 10 12 14 16 18]
I tried implementing fib() using an anonymous sub
my $fib = -> Int $x --> Int {
return 0 if $x == 0;
return 1 if $x == 1;
return $fib($x - 1) + $fib($x - 2);
}
$fib(13)
I get the following error when running that with explicit returns.
Attempt to return outside of any Routine
in block at test.p6 line 39
So I got rid of the return values.
my $fib = -> Int $x --> Int {
0 if $x == 0;
1 if $x == 1;
$fib($x - 1) + $fib($x - 2);
}
say $fib(13);
This last version never returns. Is there a way to write an anonymous recursive function without return values?
According to the documentation :
Blocks that aren't of type Routine (which is a subclass of Block) are
transparent to return.
sub f() {
say <a b c>.map: { return 42 };
# ^^^^^^ exits &f, not just the block }
The last statement is the implicit return value of the block
So you can try:
my $fib = -> Int $x --> Int {
if ( $x == 0 ) {
0; # <-- Implicit return value
}
elsif ( $x == 1 ) {
1; # <-- Implicit return value
}
else {
$fib($x - 1) + $fib($x - 2); # <-- Implicit return value
}
}
Three more options:
sub
You can write anonymous routines by using sub without a name:
my $fib = sub (Int $x --> Int) {
return 0 if $x == 0;
return 1 if $x == 1;
return $fib($x - 1) + $fib($x - 2);
}
say $fib(13); # 233
See #HåkonHægland's answer for why this (deliberately) doesn't work with non-routine blocks.
leave
The design anticipated your question:
my $fib = -> Int $x --> Int {
leave 0 if $x == 0;
leave 1 if $x == 1;
leave $fib($x - 1) + $fib($x - 2);
}
compiles. Hopefully you can guess that what it does -- or rather is supposed to do -- is exactly what you wanted to do.
Unfortunately, if you follow the above with:
say $fib(13);
You get a run-time error "leave not yet implemented".
My guess is that this'll get implemented some time in the next few years and the "Attempt to return outside of any Routine" error message will then mention leave. But implementing it has very low priority because it's easy to write sub as above, or write code as #HåkonHægland did, or use a case/switch statement construct as follows, and that's plenty good enough for now.
case/switch (when/default)
You can specify the parameter as $_ instead of $x and then you're all set to use constructs that refer to the topic:
my $fib = -> Int $_ --> Int {
when 0 { 0 }
when 1 { 1 }
$fib($_ - 1) + $fib($_ - 2)
}
say $fib(13); # 233
See when.
Blocks don't need to declare the return type. You can still return whatever you want, though. The problem is not in using return, it's in the declaration of the Int.
use v6;
my $fib = -> Int $x {
if $x == 0 {
0;
} elsif $x == 1 {
1;
} else {
$fib($x - 1) + $fib($x - 2);
}
}
say $fib(13) ;
The problem is that the return value needs to be the last executed. In the way you have done it, if it finds 0 or 1 it keeps running, getting to the last statement, when it will start all over again.
Alternatively, you can use given instead of the cascaded ifs. As long as whatever it returns is the last issued, it's OK.

Recursive generator - manual zip vs operator

Here's exercise 5.F.2 from 'A Book of Abstract Algebra' by Charles C Pinter:
Let G be the group {e, a, b, b^2, b^3, ab, ab^2, ab^3} whose
generators satisfy a^2 = e, b^4 = e, ba = ab^3. Write the table
of G. (G is called the dihedral group D4.)
Here's a little Perl 6 program which presents a solution:
sub generate(%eqs, $s)
{
my #results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { #results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { #results.push($s.subst(/$val/, $key)); }
}
for #results -> $result { take $result; }
my #arrs = #results.map({ gather generate(%eqs, $_) });
my $i = 0;
while (1)
{
for #arrs -> #arr { take #arr[$i]; }
$i++;
}
}
sub table(#G, %eqs)
{
printf " |"; for #G -> $y { printf "%-5s|", $y; }; say '';
printf "-----|"; for #G -> $y { printf "-----|"; }; say '';
for #G -> $x {
printf "%-5s|", $x;
for #G -> $y {
my $result = (gather generate(%eqs, "$x$y")).first(* (elem) #G);
printf "%-5s|", $result;
}
say ''
}
}
# ----------------------------------------------------------------------
# Pinter 5.F.2
my #G = <e a b bb bbb ab abb abbb>;
my %eqs = <aa e bbbb e ba abbb>; %eqs<e> = '';
table #G, %eqs;
Here's what the resulting table looks like:
Let's focus on these particular lines from generate:
my #arrs = #results.map({ gather generate(%eqs, $_) });
my $i = 0;
while (1)
{
for #arrs -> #arr { take #arr[$i]; }
$i++;
}
A recursive call to generate is made for each of the items in #results. Then we're effectively performing a manual 'zip' on the resulting sequences. However, Perl 6 has zip and the Z operator.
Instead of the above lines, I'd like to do something like this:
for ([Z] #results.map({ gather generate(%eqs, $_) })).flat -> $elt { take $elt; }
So here's the full generate using Z:
sub generate(%eqs, $s)
{
my #results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { #results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { #results.push($s.subst(/$val/, $key)); }
}
for #results -> $result { take $result; }
for ([Z] #results.map({ gather generate(%eqs, $_) })).flat -> $elt { take $elt; }
}
The issue with the Z version of generate is that it hangs...
So, my question is, is there a way to write generate in terms of Z?
Besides this core question, feel free to share alternative solutions to the exercise which explore and showcase Perl 6.
As another example, here's exercise 5.F.3 from the same book:
Let G be the group {e, a, b, b^2, b^3, ab, ab^2, ab^3} whose
generators satisfy a^4 = e, a^2 = b^2, ba = ab^3. Write the
table of G. (G is called the quaternion group.)
And the program above displaying the table:
As an aside, this program was converted from a version in C#. Here's how generate looks there using LINQ and a version of ZipMany courtesy of Eric Lippert.
static IEnumerable<string> generate(Dictionary<string,string> eqs, string s)
{
var results = new List<string>();
foreach (var elt in eqs)
{
if (new Regex(elt.Key).IsMatch(s))
results.Add(new Regex(elt.Key).Replace(s, elt.Value, 1));
if (new Regex(elt.Value).IsMatch(s))
results.Add(new Regex(elt.Value).Replace(s, elt.Key, 1));
}
foreach (var result in results) yield return result;
foreach (var elt in ZipMany(results.Select(elt => generate(eqs, elt)), elts => elts).SelectMany(elts => elts))
yield return elt;
}
The entire C# program: link.
[2022 update by #raiph. I just tested the first block of code in a recent Rakudo. The fourth example returned one result, 'abc', rather than none. This may be due to a new Raku design decision / roast improvement / trap introduced since this answer was last edited (in 2017), or a Rakudo bug. I'm not going to investigate; I just wanted to let readers know.]
Why your use of zip doesn't work
Your code assumes that [Z] ("reducing with the zip operator") can be used to get the transpose of a list-of-lists.
Unfortunately, this doesn't work in the general case.
It 'usually' works, but breaks on one edge case: Namely, when the list-of-lists is a list of exactly one list. Observe:
my #a = <a b c>, <1 2 3>, <X Y Z>; put [Z~] #a; # a1X b2Y c3Z
my #a = <a b c>, <1 2 3>; put [Z~] #a; # a1 b2 c3
my #a = <a b c>,; put [Z~] #a; # abc
my #a; put [Z~] #a; # abc <-- 2022 update
In the first two examples (3 and 2 sub-lists), you can see that the transpose of #a was returned just fine. The fourth example (0 sub-lists) does the right thing as well.
But the third example (1 sub-list) didn't print a b c as one would expect, i.e. it didn't return the transpose of #a in that case, but rather (it seems) the transpose of #a[0].
Sadly, this is not a Rakudo bug (in which case it could simply be fixed), but an unforseen interaction of two Perl 6 design decisions, namely:
The reduce meta-operator [ ] handles an input list with a single element by calling the operator it's applied to with one argument (said element).
In case you're wondering, an infix operator can be called with only one argument by invoking its function object: &infix:<Z>( <a b c>, ).
The zip operator Z and function zip (like other built-ins that accept nested lists), follows the so-called "single-argument rule" – i.e. its signature uses a single-argument slurpy parameter. This means that when it is called with a single argument, it will descend into it and consider its elements the actual arguments to use. (See also Slurpy conventions.)
So zip(<a b c>,) is treated as zip("a", "b", "c").
Both features provide some nice convenience in many other cases, but in this case their interaction regrettably poses a trap.
How to make it work with zip
You could check the number of elements of #arrs, and special-case the "exactly 1 sub-list" case:
my #arrs = #results.map({ gather generate(%eqs, $_) });
if #arrs.elems == 1 {
.take for #arrs[0][];
}
else {
.take for flat [Z] #arrs
}
The [] is a "zen slice" - it returns the list unchanged, but without the item container that the parent Array wrapped it in. This is needed because the for loop would consider anything wrapped in an item container as a single item and only do one iteration.
Of course, this if-else solution is not very elegant, which probably negates your reason for trying to use zip in the first place.
How to write the code more elegantly without zip
Refer to Christoph's answer.
It might be possible with a Z, but for my poor little brain, zipping recursively generated lazy lists is too much.
Instead, I did some other simplifications:
sub generate($s, %eqs) {
take $s;
# the given equations normalize the string, ie there's no need to apply
# the inverse relation
for %eqs.kv -> $k, $v {
# make copy of $s so we can use s/// instead of .subst
my $t = $s;
generate $t, %eqs
if $t ~~ s/$k/$v/;
}
}
sub table(#G, %eqs) {
# compute the set only once instead of implicitly on each call to (elem)
my $G = set #G;
# some code golfing
put ['', |#G]>>.fmt('%-5s|').join;
put '-----|' x #G + 1;
for #G -> $x {
printf '%-5s|', $x;
for #G -> $y {
printf '%-5s|', (gather generate("$x$y", %eqs)).first(* (elem) $G);
}
put '';
}
}
my #G = <e a b bb bbb ab abb abbb>;
# use double brackets so we can have empty strings
my %eqs = <<aa e bbbb e ba abbb e ''>>;
table #G, %eqs;
Here is a compact rewrite of generate that does bidirectional substitution, still without an explicit zip:
sub generate($s, %eqs) {
my #results = do for |%eqs.pairs, |%eqs.antipairs -> (:$key, :$value) {
take $s.subst($key, $value) if $s ~~ /$key/;
}
my #seqs = #results.map: { gather generate($_, %eqs) }
for 0..* -> $i { take .[$i] for #seqs }
}
Here's a version of generate that uses the approach demonstrated by smls:
sub generate(%eqs, $s)
{
my #results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { #results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { #results.push($s.subst(/$val/, $key)); }
}
for #results -> $result { take $result; }
my #arrs = #results.map({ gather generate(%eqs, $_) });
if #arrs.elems == 1 { .take for #arrs[0][]; }
else { .take for flat [Z] #arrs; }
}
I've tested it and it works on exercises 2 and 3.
As smls mentions in his answer, zip doesn't do what we were expecting when the given array of arrays only contains a single array. So, let's make a version of zip which does work with one or more arrays:
sub zip-many (#arrs)
{
if #arrs.elems == 1 { .take for #arrs[0][]; }
else { .take for flat [Z] #arrs; }
}
And now, generate in terms of zip-many:
sub generate(%eqs, $s)
{
my #results = ();
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { #results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { #results.push($s.subst(/$val/, $key)); }
}
for #results -> $result { take $result; }
zip-many #results.map({ gather generate(%eqs, $_) });
}
That looks pretty good.
Thanks smls!
smls suggests in a comment below that zip-many not invoke take, leaving that to generate. Let's also move flat from zip-many to generate.
The slimmed down zip-many:
sub zip-many (#arrs) { #arrs == 1 ?? #arrs[0][] !! [Z] #arrs }
And the generate to go along with it:
sub generate(%eqs, $s)
{
my #results;
for %eqs.kv -> $key, $val {
if $s ~~ /$key/ { #results.push($s.subst(/$key/, $val)); }
if $s ~~ /$val/ { #results.push($s.subst(/$val/, $key)); }
}
.take for #results;
.take for flat zip-many #results.map({ gather generate(%eqs, $_) });
}
Testing the keys and values separately seems a bit silly; your strings aren't really regexes, so there's no need for // anywhere in your code.
sub generate($s, #eqs) {
my #results = do for #eqs.kv -> $i, $equation {
take $s.subst($equation, #eqs[ $i +^ 1 ]) if $s.index: $equation
}
my #seqs = #results.map: { gather generate($_, #eqs) }
for 0..* -> $i { take .[$i] for #seqs }
}
Obviously with this version of generate you'll have to rewrite table to use #eqs instead of %eqs.