Recursive Cinderella
PWC 184 asked to split an array into numbers and letters.
for ( 'a 1 2 b 0', '3 c 4 d'), ( '1 2', 'p q r', 's 3', '4 5 t') -> @a is copy {
@a.=map: *.split(' ').cache;
my @numbers = @a.deepmap({ / <[0..9]> / ?? .Numeric !! Empty }).grep(+*)».Array;
my @letters = @a.deepmap({ / <[a..z]> / ?? .Str !! Empty }).grep(+*)».Array;
say @numbers, ‘ and ’, @letters;
}
I use deepmap
with Empty
to separate to weed from the chaff and remove the extra structure added by deepmap
with a grep
. That extra structure raised the question, what would be needed to split a tree in twain. This could be useful to gain parts of a XML Document with a single parser pass, while maintaining the structure of the tree. Relation of child nodes and parent nodes often carries meaning.
for ( 'a 1 2 b 0', '3 c 4 d'), ( '1 2', 'p q r', 's 3', '4 5 t'), [ '1', [ [ 'a' ], '2 b'], '3 c' ] -> @a is copy {
multi sub cinderella(@data, @a, $needle-a, @b, $needle-b) {
for @data {
@a[$++] := my $new-a;
@b[$++] := my $new-b;
cinderella($_, $new-a, $needle-a, $new-b, $needle-b)
}
}
multi sub cinderella(@data, Any:U $a is raw, $needle-a, Any:U $b is raw, $needle-b) {
my @new-a;
my @new-b;
for @data {
cinderella($_, $a, $needle-a, $b, $needle-b);
@new-a.push: $a;
@new-b.push: $b;
}
$a = @new-a;
$b = @new-b;
}
multi sub cinderella(Str:D $s, $a is raw, $needle-a, $b is raw, $needle-b) {
cinderella($_, my @new-a, $needle-a, my @new-b, $needle-b) for $s.split(' ');
$a = @new-a ?? @new-a.join(' ') !! Empty;
$b = @new-b ?? @new-b.join(' ') !! Empty;
}
multi sub cinderella(Str:D $s where *.chars == 1, @a, $needle-a, @b, $needle-b) {
given $s {
when $needle-a { @a.push: $s }
when $needle-b { @b.push: $s }
default { fail('dunno where to put: ' ~ $s) }
}
}
cinderella @a, my @numbers, / <[0..9]> /, my @letters, / <[a..z]> /;
dd @numbers, @letters;
}
# OUTPUT: Array @numbers = ["1 2 0", "3 4"]
Array @letters = ["a b", "c d"]
Array @numbers = ["1 2", Empty, "3", "4 5"]
Array @letters = [Empty, "p q r", "s", "t"]
Array @numbers = ["1", [[[],], "2"], "3"]
Array @letters = [[], [["a"], "b"], "c"]
The leaves in the target tree can be either a Str
or (empty) Array
. However, the first multi candidate doesn’t make the decision what item is added to the target Array
s. Instead I create a fresh scalar container and bind it to a container within the Array
s. I then pass that container on to the next multi
-candidate where it might be filled with Positional
– or Str
-object. Please note the distinction. MMD doesn’t care about the container type we use, it looks for the properties of the value. This allows me to split the strings on a white space and pass it on into the next round of MMD matches. The 2nd candidate handles the case where we descent into a nested Array
. It can manipulate the scalar container created with @a[$++] := my $new-a;
and turn it into a Positional value (here an Array
), because that initial container is passed into the multi with is raw
.
This is a very powerful concept. Writing the same with a single recursive function would contain a lot of special casing and be no joy to debug.
Not doing what the instructor asked for seems to produce better results. I shall do so more often.
-
October 10, 2022 at 19:322022.41 On Your Phone – Rakudo Weekly News