Wherein we find the hidden sequences and hopscotch our way forward towards our goal, carefully, one square at a time. But stay in your lane, friend. Stay in your lane.
THE WEEKLY CHALLENGE – PERL & RAKU #131
episode one:
“The Porte-Manteau Carries Itself Up The Stair, Step by Step”
Task 1
Consecutive Arrays
Submitted by: Mark Anderson
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])
Method
I often espouse the credo that if you really want to understand something, try explaining it to someone else. As you progress through the explanation it will soon become apparent where any missing pieces are in your comprehension of whatever it is you think you know. The person in question may or may not know or care much about the subject matter, and in fact when it comes down to it they don’t even, strictly speaking, need to know they are being spoken to at all. Although a dyadic conversation is certainly preferential — the recipient can add directed questions to fill out any gaps and possibly reveal overlooked areas — in a pinch inanimate objects work fine too:

In the context of the immediate problem, I feel this challenge is one where understanding what is being asked for — clearly stating the circumstances and the outcome — ultimately explains how to accomplish the goal. The short text description is not in itself vague, but not what one would call super-helpful either. A quick look at the many examples, however, makes the request clear: we want to group together sequential numbers into lists, and if a number is not a continuation of an existing greater sequence then it becomes a stand-alone sequence of one.
Our process, then, is to examine each array element in turn and somehow add it to the output. If the value is one greater than the last value in the last list, that is to say it continues it, then we add it to that list, and if not then we finish the previous list and start a new list with the new value. When you put it like that the steps are pretty straightforward. For each item we pick up we put it into one of two places.
PERL 5 SOLUTION
I ended up spending inordinate time, for some reason1 on getting the output to exactly match the examples. As we are dealing with multi-dimensional arrays, this nesting of data inside brackets and parentheses gets a little tricky. I did manage to avoid a further nested join
statement, though, by locally redefining the List Separator variable, $"
. This is the special string placed between characters when a list is interpolated into a string, such as occurs within double quotes. Changing it from the default single space to a comma-space combination gives us a nice comma-separated list of values to place inside the square brackets. In a string context the brackets don’t mean anything, of course, and are just characters added to craft the output.
The main logic breaks the input into an array of characters, and the first element is shifted into a new anonymous array reference. The array itself is anonymous, but the reference to its location is held in the $subarray
variable, allowing us to easily swap out the data structure pointed to for a new one whenever we want. Then each remaining element is examined and either placed into the existing anonymous array if it’s properly in sequence there, or the $subarray
reference is placed on the output array and the element in question starts its own, new, anonymous array, replacing the previous. We could use an if / else
control structure but a ternary operator makes this either/or choice quite clear.
One more thing I’d like to add is I’m really enjoying the modern Perl ability to indent a heredoc-style printing with the addition of the tilde, ~
. The indent just makes everything so much easier to scan, and differentiate the output from the control flow.
1 it amused me, of course. That is always the reason.
use warnings;
use strict;
use utf8;
use feature ":5.26";
use feature qw(signatures);
no warnings 'experimental::signatures';
sub consequential ( @input ) {
my @out;
my $subarray = [ shift @input ];
for (@input) {
$_ == $subarray->[-1] + 1
? push $subarray->@*, $_
: do {
push @out, $subarray;
$subarray = [ $_ ];
}
}
return @out, $subarray;
}
my @tests = ( [1, 2, 3, 6, 7, 8, 9],
[11, 12, 14, 17, 18, 19],
[2, 4, 6, 8],
[1, 2, 3, 4, 5] );
local $" = ', ';
for (@tests) {
my $input = '(' . (join ', ', $_->@*) . ')';
my $output = '(' . (join ', ', map {"[$_->@*]"} consequential( $_->@* ) ) . ')';
say <<~"END";
input $input
output $output
END
}
raku solution
In Raku the control flows much the same. Things are either made clearer (or perhaps more complicated depending on how you look at it) when we need to decontainerize the values held in the @subarray
variable and push them to output in a new anonymous array. This seemed the easiest way to make @subarray
point to a new array and not overwrite the old.
unit sub MAIN ( @input? ) ;
@input.elems == 0
&& @input = 11, 12, 14, 17, 18, 19;
my @out;
our @subarray = [ @input.shift ];
for @input {
$_ == @subarray[*-1] + 1
?? @subarray.push: $_
!! do {
@out.push: [ |@subarray ] ;
@subarray = [ $_ ]
}
}
@out.push: @subarray;
@out.say;
episode two:
“Pairing Up and Paring Down”
task 2
Find Pairs
Submitted by: Yary
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:
/**/<
/**/>
Method
Oh dear… The harder you look the messier this problem gets. Nested delimiters get
particularly so… Where do we start? We’re going to have to make some observations, and requisite assumptions:
- With matching delimiters, the second occurrence should close the first.
- A closing delimiter from one pair cannot be the opener for a different pair. That would get crazy quick.
- A delimiter is only one character. We will take guidance from example 2, which breaks the common comment pair
/*
into two individual delimiters, matching up with themselves to open and close. - Should we acknowledge a close without an open before it? Hmmm…
- Can delimited areas incompletely overlap, so one starts before the other stops? What is being delimited here anyway?
- I feel like I’m at the edge of a very deep rabbit-hole…
BUT… hol’ up a minute. Pause, take a deep breathe and start again. Let’s rewind and return to the theme of understanding exactly what is being asked for, as was brought up before.
Evidently our snippet of premature analysis is not the problem at all. The problem wants a report of a filtered list of characters from the open set and the same from the closed. That’s it. No counting openings and closings — none of that. We’ll break it into an array of chars and use grep
, then rejoin the list into a string. Easy-peasy.
Keeping track of actual delimiter pairs, though? I think the best way through that minefield is to think carefully about exactly what you will allow. It’s a lot like formatting in CSV — it seems like there’s always another pathological case to consider.
I’m quite relieved that wasn’t the problem. This version seems almost uneventful in comparison.
PERL 5 SOLUTION
use warnings;
use strict;
use utf8;
use feature ":5.26";
use feature qw(signatures);
no warnings 'experimental::signatures';
## ex-1
# my $delims = q|""[]()|;
# my $input = q|"I like (parens) and the Apple ][+" they said.|;
## ex-2
my $delims = q|**//<>|;
my $input = q|/* This is a comment (in some languages) */ <could be a tag>|;
my ($open, $close) = parse_delimiters( $delims );
my @input = split //, $input;
my $o_chars = join '', grep { exists $open->{$_} } @input;
my $c_chars = join '', grep { exists $close->{$_} } @input;
say <<~"END";
input $input
delims $delims
opens $o_chars
closes $c_chars
END
sub parse_delimiters ( $pair_str ) {
my ( %open, %close );
while (length $pair_str > 0) {
$open{ substr $pair_str, 0, 1, '' } = undef;
$close{ substr $pair_str, 0, 1, '' } = undef;
}
return \%open, \%close;
}
Raku Solution
unit sub MAIN ( $delimiters = q|**//<>|,
$input = q|/* This is a comment (in some languages) */ <could be a tag>|
) ;
my (%delim, %chars);
for $delimiters.comb.rotor(2) -> ($o, $c) {
%delim<o>{$o} = %delim<c>{$c} = True;
}
for "o", "c" -> $dtype {
%chars{$dtype} = $input.comb
.grep( {%delim{$dtype}{$_}:exists} )
.join ;
}
say qq:to/END/;
input $input
delimiters $delimiters
opens {%chars<o>}
closes {%chars<c>}
END
The Perl Weekly Challenge, that idyllic glade wherein we stumble upon the holes for these sweet descents, is now known as
The Weekly Challenge – Perl and Raku
It is the creation of the lovely Mohammad Sajid Anwar and a veritable swarm of contributors from all over the world, who gather, as might be expected, weekly online to solve puzzles. Everyone is encouraged to visit, learn and contribute at