I’ve been doing the Weekly
Challenges. The
latest
involved splitting arrays and strings. (Note that this is open
until 26 September 2021.)
Task 1: Consecutive Sub-Arrays
You are given a sorted list of unique positive integers.
Write a script to return list of arrays where the sub-arrays are
consecutive integers.
Well, it's a little unexpected, but we can do that fairly cleanly.
Here I'm using the Ruby version, since I haven't shown that for a
while.
def csa(list)
The first sublist in the output holds the first value.
o=[[list[0]]]
Skip that and proceed from the next value…
1.upto(list.length-1) do |ni|
If it isn't the next expected value in a consecutive list, create a
new empty sublist.
unless o[-1][-1]+1 == list[ni] then
o.push([])
end
Then drop the value into the last sublist (either the newly created
one or the previous one that already has content).
o[-1].push(list[ni])
end
return o
end
It would be nice not to have to handle the indices directly, such as
by using for n in lst
, but then there'd be a conditional inside the
loop to skip the first value. Or I could make a copy of the list and
remove its first value. Neither of these is ideal. In the Rust and
Python versions I explicitly define an iterator based on the list
contents (which I hope should be implemented as a pointer rather than
copying the whole list), call next()
on it once to throw away the
first value, then loop on what's left; I think Raku can do this too,
with a bit more faff. The Rust version:
let mut i=list.iter();
i.next();
for n in i {
Ruby has things it calls "iterators", but they seem to be its term for
method calls to iterate over a block, and I don't know if it has the
gaps in it to let me get my pincers in between "define the things over
which I will loop" and "loop over them".
Task 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.
Key point here: the same character may occur in both "opening" and
"closing" delimiters, in which case it's added to both output
strings each time it occurs. Also there's no escaping or consideration
of matched opening/closing pairs. So what I do is build a hash of
"opening" and "closing" delimiter characters (a set, in the languages
which support sets, which is all of them except Perl since I don't
need to remove keys from the set this time), then check each
character in the search string to see whether it appears in either (or
both) sets.
In Perl/Raku one might reach for a regexp but punctuation characters
are a pain, and one of the reasons I'm doing this in multiple
languages is to get out of the habits formed by Perl where regexps and
hash key lookups are ferociously optimised and one uses them for
everything. Anyway, here's the Perl.
sub fp {
my ($delims,$sample)=@_;
Iterate through the delimiter string noting opening and closing
characters.
my @d;
foreach my $i (0..length($delims)-1) {
$d[$i % 2]->{substr($delims,$i,1)}=1;
}
my @o;
foreach my $s (split '',$sample) {
Test each character of the sample string for being in the openers
and/or closers hashes, and push to a list if so.
foreach my $x (0,1) {
if (exists $d[$x]->{$s}) {
push @{$o[$x]},$s;
}
}
}
Turn the lists into strings.
return [map {join('',@{$_})} @o];
}
The Raku is a bit uglier because I need to define the arrays
explicitly so that they'll be mutable rather than default immutable
Lists. In the Rust version, I'm especially happy with the
char_indices
method on the delims
string, which returns a series
of (index, character)
tuples so that I don't have to manage the loop
explicitly.
The PostScript is crude, with the open and close lists as separate
variables (yeah, I should have put them in an array) and setting
characters directly into fixed-length strings, but it works…
/fp {
exch
dup length
dup dict /sopen exch def
dict /sclose exch def
/i 0 def
{
i 0 eq {
sopen exch 1 put
} {
sclose exch 1 put
} ifelse
/i 1 i sub def
} forall
dup length
dup string /copen exch def
string /cclose exch def
/iopen 0 def
/iclose 0 def
{
dup
dup sopen exch known {
copen exch iopen exch put
/iopen iopen 1 add def
} {
pop
} ifelse
dup sclose exch known {
cclose exch iclose exch put
/iclose iclose 1 add def
} {
pop
} ifelse
} forall
cclose copen
} def
Full code on
github.
Comments on this post are now closed. If you have particular grounds for adding a late comment, comment on a more recent post quoting the URL of this one.