by Arne Sommer

# Con Se Pair with Raku and Perl

[147] Published 25. September 2021.

This is my response to the Perl Weekly Challenge #131.

## Challenge #131.1: Consecutive Arrays

You are given a sorted list of unique positive integers.

Write a script to return list of arrays where the arrays are consecutive integers.

Example 1: ```Input: (1, 2, 3, 6, 7, 8, 9) Output: ([1, 2, 3], [6, 7, 8, 9]) ``` Example 2: ```Input: (11, 12, 14, 17, 18, 19) Output: ([11, 12], [14], [17, 18, 19]) ``` Example 3: ```Input: (2, 4, 6, 8) Output: ([2], [4], [6], [8]) ``` Example 4: ```Input: (1, 2, 3, 4, 5) Output: ([1, 2, 3, 4, 5]) ```

Let us dive straight in:

File: coar ```#! /usr/bin/env raku multi MAIN (Str \$input = "1 2 3 6 7 8 9", :v(:\$verbose)) # [1] { my @input = \$input.words; # [1a] MAIN(@input, :\$verbose); # [1b] } multi MAIN (*@input where @input.elems > 1 && all(@input) ~~ /^\d+\$/, :v(:\$verbose)) # [2] { die "Not sorted" unless [<] @input; # [3] my @result; # [4] my \$current = @input.shift; # [5] my @current = \$current,; # [6] say ": Candidate: \$current" if \$verbose; for @input -> \$i # [7] { if \$i > \$current +1 # [8] { @result.push: @current.clone; # [8a] say ": Push: [", @current.join(","), "]" if \$verbose; @current = (); # [8b] } say ": Candidate: \$i" if \$verbose; @current.push: \$i; # [9] \$current = \$i; # [10] } @result.push: @current if @current; # [11] say @result; # [12] } ```

[1] How to specify the input? This version of «multi::Main» takes a single (space separated) string, splits the string into separate values [1a], and and calls the other «Multi MAIN» with those values [1b].

[2] This version of «multi MAIN» takes a list of values. The `where` clause ensures that we have at least one element in the list (as a slurpy array can be empty). Then we use a regex and a junction to ensure that all the values are integers.

Note that coercing the values to integers (with `\$input.words>>.Int` in e.g. [1a]) will truncate non-integer numbers. Non-numbers will cause an error. Coercing them to numbers (with `\$input.words>>.Numeric` is the thing, as it does not play havoc with non-integer numbers. But it does not check that the numbers are integers, and is useless here.

[3] The reduction metaoperator `[]` (with an operator, code block or procedure call in the middle) ensures that each value is smaller than the next one.

[4] The result (the array of arrays) will end up here.

[5] Get the first input value.

[6] Store it in a list. Note the `,` (comma), which is the list operator.

[7] Iterate over the rest of the values (after the first one; see [5]).

[8] Do we have non-consecutive values? If so, add the array of values to the result [8a]. Note the `clone`, as we add a reference - and would end up with an array with a lot of identical inner arrays if we did not. Then we clear out the inner array [8b].

[9] Add the value to the inner array (which may or may not be empty by now).

[10] Set the current value, ready for the next iteration.

[11] Add a final inner array, if any.

[12] Print the result.

See docs.raku.org/syntax/multi for more information about `multi`.

See https://docs.raku.org/routine/MAIN for more information about `MAIN`.

See docs.raku.org/language/operators#Reduction_metaoperators for more information about the Reduction Metaoperator `[]`.

See docs.raku.org/routine/, for more information about the list operator `,`.

See docs.raku.org/routine/clone for more information about the `clone` method.

Running it on the first example, first with the default values, then with the values as a string, and finally as separate input strings.

```\$ ./coar [[1 2 3] [6 7 8 9]] \$ ./coar "1 2 3 6 7 8 9" [[1 2 3] [6 7 8 9]] \$ ./coar 1 2 3 6 7 8 9 [[1 2 3] [6 7 8 9]] ```

All the examples, with verbose mode:

```\$ ./coar -v 1 2 3 6 7 8 9 : Candidate: 1 : Candidate: 2 : Candidate: 3 : Push: [1,2,3] : Candidate: 6 : Candidate: 7 : Candidate: 8 : Candidate: 9 [[1 2 3] [6 7 8 9]] \$ ./coar -v 11 12 14 17 18 19 : Candidate: 11 : Candidate: 12 : Push: [11,12] : Candidate: 14 : Push: [14] : Candidate: 17 : Candidate: 18 : Candidate: 19 [[11 12] [14] [17 18 19]] \$ ./coar -v 2 4 6 8 : Candidate: 2 : Push: [2] : Candidate: 4 : Push: [4] : Candidate: 6 : Push: [6] : Candidate: 8 [[2] [4] [6] [8]] \$ ./coar -v 1 2 3 4 5 : Candidate: 1 : Candidate: 2 : Candidate: 3 : Candidate: 4 : Candidate: 5 [[1 2 3 4 5]] ```

Note that simply printing the result, as I have done here works out. But the result is not quite as specified in the challenge. Fixing that is easy-ish:

File: coar-fixed (changes only) ``` say '(' ~ @result.map({ '[' ~ @\$_.join(', ') ~ ']' }).join(', ') ~ ')'; ```

The `map` block is applied to every top level value in the list, either a single value or a sublist. If there are more than one value, they are combined with commas. The resulting string is placed in brackets (`[` and `]`). The resulting strings are combined with commas, and surrounded in parens (`(` and `)`). Easy-ish, indeed…

Running it gives the required output:

```\$ ./coar-fixed ([1, 2, 3], [6, 7, 8, 9]) \$ ./coar-fixed 11 12 14 17 18 19 ([11, 12], [14], [17, 18, 19]) \$ ./coar-fixed 2 4 6 8 ([2], [4], [6], [8]) \$ ./coar-fixed 1 2 3 4 5 ([1, 2, 3, 4, 5]) ```

### A Perl Version

This is a straight forward translation of the Raku version, without the default value, and it does not support a single string.

File: coar-perl ```#! /usr/bin/env perl use strict; use warnings; use feature 'say'; use Getopt::Long; use Perl6::Junction 'all'; # [1] my \$verbose = 0; GetOptions("verbose" => \\$verbose); die "Integers only" unless all(@ARGV) == qr/^\d+\$/; # [1] my @result; my \$current = shift(@ARGV) // die "No numbers"; my @current = (\$current); say ": Candidate: \$current" if \$verbose; for my \$i (@ARGV) { if (\$i > \$current +1) { my @copy = @current; push(@result, \@copy); #clone?? say ": Push: [", join(",", @current), "]" if \$verbose; @current = (); } die "Not sorted (\$current < \$i)" unless \$i > \$current; # [2] say ": Candidate: \$i" if \$verbose; push(@current, \$i); \$current = \$i; } push(@result, \@current) if @current; say "(", join(", ", map { "[" . join(", ", @\$_) . "]" } @result), ")"; ```

[1] Junctions make life easier (for me as a programmer), so I use this Perl module to get the «all» function.

[2] Perl does not have something similar to the reduction metaoperator in Raku, but a cleverly designed test in the loop does the trick.

Running it gives the same result as the Raku version:

```\$ ./coar-perl 1 2 3 6 7 8 9 ([1, 2, 3], [6, 7, 8, 9]) \$ ./coar-perl 11 12 14 17 18 19 ([11, 12], [14], [17, 18, 19]) \$ ./coar-perl 2 4 6 8 ([2], [4], [6], [8]) \$ ./coar-perl 1 2 3 4 5 ([1, 2, 3, 4, 5]) ```

Note that simply printing the result does not work at all in Perl (as opposed to Raku). The result will be something like this:

```say @array; # -> ARRAY(0x564db2477e60)ARRAY(0x564db22ee978) say @array; # -> ARRAY(0x559f4330fe80) ```

The first one is from the first example, and the second one is from the fourth example.

## Challenge #131.2: Find Pairs

You are given a string of delimiter pairs and a string to search.

Write a script to return two strings, the first with any characters matching the “opening character” set, the second with any matching the “closing character” set.

Example 1: ```Input: Delimiter pairs: ""[]() Search String: "I like (parens) and the Apple ][+" they said. Output: "([" ")]" ``` Example 2: ```Input: Delimiter pairs: **//<> Search String: /* This is a comment (in some languages) */ <could be a tag> Output: /**/< /**/> ```
File: find-pairs ```#! /usr/bin/env raku unit sub MAIN (\$pairs = '""[]()', \$search = '"I like (parens) and the Apple ][+" they said.', :v(:\$verbose)); my @pairs = \$pairs.comb; # [1] my @open = @pairs[0, 2 ... *]; # [2] my @close = @pairs[1, 3 ... *]; # [3] say ": Open: @open[]" if \$verbose; say ": Close @close[]" if \$verbose; my \$open = ""; # [4] my \$close = ""; # [4a] for \$search.comb -> \$char # [5] { \$open ~= \$char if any(@open) eq \$char; # [6] \$close ~= \$char if any(@close) eq \$char; # [7] } say \$open; # [8] say \$close; # [8] ```

[1] Get the individual characters in the delimiter pairs.

[2] Get the characters with an even index (i.e. the starting delimiters). Note the use of an array slice, which works even if the indices are out of bounds.

[3] Get the characters with an odd index (i.e. the ending delimiters).

[4] The opening matches will go here, and ditto for the closing matches [4a].

[5] Iterate over the input string, one character at a time.

[6] Add it (the character) to the opening matches string if is one of the opening characters.

{7] Ditto for the closing matches and characters.

[8] Print the result.

Running it:

```\$ ./find-pairs "([" ")]" \$ ./find-pairs '**//<>' \ '/* This is a comment (in some languages) */ <could be a tag>' /**/< /**/> ```

Looking good.

With verbose mode:

```\$ ./find-pairs -v '""[]()' '"I like (parens) and the Apple ][+" they said.' : Open: " [ ( : Close " ] ) "([" ")]" \$ ./find-pairs -v '**//<>' \ '/* This is a comment (in some languages) */ <could be a tag>' : Open: * / < : Close * / > /**/< /**/> ```

### Perl

This is a straight forward translation of the Raku version.

File: find-pairs-perl ```#! /usr/bin/env perl use strict; use warnings; use feature 'all'; use Getopt::Long; use Perl6::Junction 'all'; my \$verbose = 0; GetOptions("verbose" => \\$verbose); my \$pairs = shift(@ARGV) // '""[]()'; # [1] my \$search = shift(@ARGV) // '"I like (parens) and the Apple ][+" they said.'; my @pairs = split(//, \$pairs); my @search = split(//, \$search); my @open; my @close; for my \$index (0 .. @pairs -1) # [2] { \$index % 2 ? push(@open, \$pairs[\$index]) : push(@close, \$pairs[\$index]); } say ": Open: @open" if \$verbose; say ": Close @close" if \$verbose; my \$open = ""; my \$close = ""; for my \$char (@search) { \$open .= \$char if any(@open) eq \$char; \$close .= \$char if any(@close) eq \$char; } say \$open; say \$close; ```

[1] I have chosen to have default values this time.

[2] Raku's clever array slices is not available in Perl, but the hard way works just fine.

Running it gives the same result as the Raku version:

```\$ ./find-pairs-perl -v '""[]()' \ '"I like (parens) and the Apple ][+" they said.' : Open: " ] ) : Close " [ ( ")]" "([" \$ ./find-pairs-perl -v '**//<>' \ '/* This is a comment (in some languages) */ <could be a tag>' : Open: * / > : Close * / < /**/> /**/< ```

And that's it.