This is my response to The Weekly Challenge #316.
Input: @list = ("perl", "loves", "scala")
Output: true
Example 2:
Input: @list = ("love", "the", "programming")
Output: false
Example 3:
Input: @list = ("java", "awk", "kotlin", "node.js")
Output: true
File: circular
#! /usr/bin/env raku
unit sub MAIN (*@list where @list.elems > 1, # [1]
:v(:$verbose));
my $ok; # [2]
for 1 .. @list.end -> $index # [3]
{
$ok = @list[$index -1].substr(@list[$index -1].chars -1)
eq @list[$index].substr(0,1); # [4]
say ": '@list[$index -1]' <=> '@list[$index]' { $ok ?? 'OK' !! '' }"
if $verbose;
last unless $ok; # [5]
}
say $ok; # [6]
[1] A slurpy array for the list of words, with at least two words.
[2] The end result, the Boolean value, initially undefined.
[3] Iterate over the indices for all the words, except the
very first one. I have used .end
that gives the index of the last
item in the array, as it is shorter than .elems -1
.
See docs.raku.org/routine/end for more information about end
.
[4] Compare the last character in the previous word
($index -1
) with the first character of the current one.
See docs.raku.org/routine/substr for more information about substr
.
[5] Not a match? Then we exit the loop, with last
.
See docs.raku.org/routine/last for more information about last
.
[6] Print the result. This will be False
if [5] kicked in,
and true
if all the tests succeeded.
Running it:
$ ./circular perl loves scala
True
$ ./circular love the programming
False
$ ./circular java awk kotlin node.js
True
Looking good.
With verbose mode:
$ ./circular -v perl loves scala
: 'perl' <=> 'loves' OK
: 'loves' <=> 'scala' OK
True
$ ./circular -v love the programming
: 'love' <=> 'the'
False
$ ./circular -v java awk kotlin node.js
: 'java' <=> 'awk' OK
: 'awk' <=> 'kotlin' OK
: 'kotlin' <=> 'node.js' OK
True
This is not in any way a circular check, merely a directed graph. But it is easy to check that the last character in the last word is the same as the first character in the first word. That would make it circular.
File: circular-circular
#! /usr/bin/env raku
unit sub MAIN (*@list where @list.elems > 1,
:c(:$circular), # [1]
:v(:$verbose));
my $ok;
@list.push: @list[0] if $circular; # [2]
for 1 .. @list.end -> $index
{
$ok = @list[$index -1].substr(@list[$index -1].chars -1)
eq @list[$index].substr(0,1);
say ": '@list[$index -1]' <=> '@list[$index]' { $ok ?? 'OK' !! '' }"
if $verbose;
last unless $ok;
}
say $ok;
[1] Enable true circular check with this command line option.
[2] Adding (a copy of) the first word at the end of our list is all that is required for a full circular check.
Some examples of running it:
$ ./circular-circular -v -c java awk kotlin node.js
: 'java' <=> 'awk' OK
: 'awk' <=> 'kotlin' OK
: 'kotlin' <=> 'node.js' OK
: 'node.js' <=> 'java'
False
$ ./circular-circular -v -c java awk kotlin node.j
: 'java' <=> 'awk' OK
: 'awk' <=> 'kotlin' OK
: 'kotlin' <=> 'node.j' OK
: 'node.j' <=> 'java' OK
True
Input: $str1 = "uvw", $str2 = "bcudvew"
Output: true
Example 2:
Input: $str1 = "aec", $str2 = "abcde"
Output: false
Example 3:
Input: $str1 = "sip", $str2 = "javascript"
Output: true
The challenge does not specify which of the strings that are the subsequence of the other one, so I support both. Multiple dispatch seemes like a good idea here.
File: subsequence (partial)
#! /usr/bin/env raku
multi MAIN ($str1, # [1]
$str2 where $str1.chars == $str2.chars, # [2]
:v(:$verbose))
{
say ": '$str1' and '$str2' have the same length" if $verbose;
say $str1 eq $str2; # [3]
}
[1] The first string. Note the use of multi
to
set up multiple procedures with the same name, but with different
signatures (argument types and/or values).
See docs.raku.org/routine/multi for more information about multi
.
[2] The second string. This version of MAIN
is used when
the two strings have the same length.
[3] The two strings have the same length. As we can only remove characters from one of them, this means we cannot remove anything, and the two string must be identical.
File: subsequence (partial)
multi MAIN ($str1,
$str2 where $str1.chars > $str2.chars, # [4]
:v(:$verbose))
{
say ": '$str1' is longer than '$str2' -> swap" if $verbose;
MAIN($str2, $str1, :$verbose); # [5]
}
[4] This version of MAIN
is used when the first string is
longer than the second one.
[5] Swap the order of the two strings, and call itself. This call
will be handled by the next and final version of MAIN
.
multi MAIN ($str1,
$str2 is copy where $str1.chars < $str2.chars, # [6]
:v(:$verbose))
{
say ": '$str1' is shorter than '$str2' " if $verbose;
for $str1.comb -> $char # [7]
{
my $i = $str2.index($char); # [8]
unless defined $i { say False; exit; } # [9]
say ": Split '$str2' = '$str2.substr(0,$i)' + \
'$char' + '$str2.substr($i +1)'" if $verbose;
$str2 = $str2.substr($i +1); # [10]
}
say True; # [11]
}
[6] This version of MAIN
is used when the first string is
shorter than the second one.
[7] Iterate over the individual characters (courtesy of comb
)
of the shortest string.
See docs.raku.org/routine/comb for more information about comb
.
[8] Use index
to get the (surprise) index of the
first occurence of the current character in the second string.
[9] If we did not find that letter, print 'False' and exit. Note that we must check for definedness as 0 is a valid index.
[10] Get rid of everyting in the second string up to and including the current search character. See the verbose mode output for examples.
[11] Failure to fail in [9] during the for
loop means success.
Say so.
Running it:
$ ./subsequence uvw bcudvew
True
$ ./subsequence aec abcde
False
$ ./subsequence sip javascript
True
Looking good.
With verbose mode:
$ ./subsequence -v uvw bcudvew
: 'uvw' is shorter than 'bcudvew'
: Split 'bcudvew' = 'bc' + 'u' + 'dvew'
: Split 'dvew' = 'd' + 'v' + 'ew'
: Split 'ew' = 'e' + 'w' + ''
True
$ ./subsequence -v aec abcde
: 'aec' is shorter than 'abcde'
: Split 'abcde' = '' + 'a' + 'bcde'
: Split 'bcde' = 'bcd' + 'e' + ''
False
$ ./subsequence -v sip javascript
: 'sip' is shorter than 'javascript'
: Split 'javascript' = 'java' + 's' + 'cript'
: Split 'cript' = 'cr' + 'i' + 'pt'
: Split 'pt' = '' + 'p' + 't'
True
Some more:
$ ./subsequence -v javascript sip
: 'javascript' is longer than 'sip' -> swap
: 'sip' is shorter than 'javascript'
: Split 'javascript' = 'java' + 's' + 'cript'
: Split 'cript' = 'cr' + 'i' + 'pt'
: Split 'pt' = '' + 'p' + 't'
True
$ ./subsequence -v perl perl
: 'perl' and 'perl' have the same length
True
$ ./subsequence -v perl "perl is the best"
: 'perl' is shorter than 'perl is the best'
: Split 'perl is the best' = '' + 'p' + 'erl is the best'
: Split 'erl is the best' = '' + 'e' + 'rl is the best'
: Split 'rl is the best' = '' + 'r' + 'l is the best'
: Split 'l is the best' = '' + 'l' + ' is the best'
True
$ ./subsequence -v perl "per list he best"
: 'perl' is shorter than 'per list he best'
: Split 'per list he best' = '' + 'p' + 'er list he best'
: Split 'er list he best' = '' + 'e' + 'r list he best'
: Split 'r list he best' = '' + 'r' + ' list he best'
: Split ' list he best' = ' ' + 'l' + 'ist he best'
True
And that's it.