Subcircular
with Raku

by Arne Sommer

Subcircular with Raku

[338] Published 9. April 2025.

This is my response to The Weekly Challenge #316.

Challenge #316.1: Circular

You are given a list of words.

Write a script to find out whether the last character of each word is the first character of the following word.

Example 1:
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

Challenge #316.2: Subsequence

You are given two strings.

Write a script to find out if one string is a subsequence of another.

A subsequence of a string is a new string that is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters.

Example 1:
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.

File: subsequence (the rest)
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.