Home > Raku > Recursive Cinderella

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 Arrays. Instead I create a fresh scalar container and bind it to a container within the Arrays. 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.

Advertisement
Categories: Raku
  1. No comments yet.
  1. October 10, 2022 at 19:32

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: