Decoded Capital
with Raku

by Arne Sommer

Decoded Capital with Raku

[210] Published 13. November 2022.

This is my response to The Weekly Challenge #190.

Challenge #190.1: Capital Detection

You are given a string with alphabetic characters only: A..Z and a..z.

Write a script to find out if the usage of Capital is appropriate if it satisfies at least one of the following rules:
  1. Only first letter is capital and all others are small
  2. Every letter is small
  3. Every letter is capital
Example 1:
Input: $s = 'Perl'
Output: 1
Example 2:
Input: $s = 'TPF'
Output: 1
Example 3:
Input: $s = 'PyThon'
Output: 0
Example 4:
Input: $s = 'raku'
Output: 1
File: capital-detection
#! /usr/bin/env raku

subset alpha where * ~~ /^<[a..z A..Z]>+$/;  # [1a]

unit sub MAIN (alpha $s);                    # [1]

say $s ~~ /^<[a..z]>+$/ || $s ~~ /^<[A..Z]>+$/ || $s ~~ /^<[A..Z]><[a..z]>+$/
 ?? 1 !! 0;                                  # [2]

[1] Ensure that the input consists of legal characters only, with the use of a custom type set up with subset.

See docs.raku.org/language/typesystem#index-entry-subset-subset for more information about subset.

[2] Print «1» if the input satisfies one (or more) of the input rules, and «0» if not.

Running it:

$ ./capital-detection Perl
1

$ ./capital-detection TPF
1

$ ./capital-detection PyThon
0

$ ./capital-detection raku
1

Looking good.

Illegal input:

$ ./capital-detection Perl5
Usage:
  ./capital-detection <s>

Challenge #190.2: Decoded List

You are given an encoded string consisting of a sequence of numeric characters: 0..9, $s.

Write a script to find the all valid different decodings in sorted order.

Encoding is simply done by mapping A,B,C,D,… to 1,2,3,4,… etc.

Example 1:
Input: $s = 11
Output: AA, K

11 can be decoded as (1 1) or (11) i.e. AA or K
Example 2:
Input: $s = 1115
Output: AAAE, AAO, AKE, KAE, KO

Possible decoded data are:
(1 1 1 5) => (AAAE)
(1 1 15)  => (AAO)
(1 11 5)  => (AKE)
(11 1 5)  => (KAE)
(11 15)   => (KO)
Example 3:
Input: $s = 127
Output: ABG, LG

Possible decoded data are:
(1 2 7) => (ABG)
(12 7)  => (LG)

Let us start with an illustration:

We start with the Todo digits (in blue). Then we pick either one or two of them (in green; in the Done list ), with the remainder still in Todo. This is a recursive task, so we go on until we end up with the result when the Todo list is empty (and everything is green). I have outlined the result in yellow.

Note that none of the examples include the digit 0 (zero). We'll have to look out for it just in case, as it will not translate into a letter (as "1" => "A"). We'll also have to look out for values > 26 (as "26" => "Z").

File: decoded-list
#! /usr/bin/env raku

# unit sub MAIN ($s where $s ~~ /^<[1..9]> <[0..9]>*$/, :v(:$verbose)); # [1]

unit sub MAIN ($s where $s ~~ /^<[0..9]>*$/, :v(:$verbose));            # [1a]

my $seq := gather { recurse( (), $s.comb); };                           # [2]

say $seq.join(", ");                                                    # [3]

sub recurse(@done is copy, @todo is copy)                               # [4]
{
  unless @todo.elems                                                    # [5]
  {
    my $string = @done.map({ ($_ + 'A'.ord - 1).chr }).join;            # [5a] 
    say ":Take: @done[] -> $string" if $verbose;

    take $string;                                                       # [5b]
    return;                                                             # [5c]
  }
  
  my $next  = @todo.shift;                                              # [6]
  my @done2 = @done.clone;                                              # [7]
  my @todo2 = @todo.clone;                                              # [7a]

  return if $next == 0;                                                 # [8]

  say ":Loop: @done2[] + $next" if $verbose;

  @done.push: $next;                                                    # [9]
  recurse(@done, @todo);                                                # [10]

  if @todo.elems                                                        # [11]
  {
    $next ~= @todo2.shift;                                              # [12]

    return if $next > 26;                                               # [13]
    
    say ":Loop: @done2[] + $next" if $verbose;
    @done2.push: $next;                                                 # [14]
    recurse(@done2, @todo2);                                            # [15]
  }
}

[1] Should we allow a leading zero in the input? I have chosen to do so, but we catch them later on (in [9]).

[2] Using gather/take to set up the sequence is ideal here, as we return values (with take) twice (potentially) in each iteration.

See my Raku Gather, I Take article or docs.raku.org/syntax/gather take for more information about gather/take.

[3] Print the result.

[4] The recursive procedure. The first argument is the part of the input that we have parsed (so far), and the second is the remainder. Note the use of is copy so that we get writeable copies.

[5] No remainder? If so convert the numbers to characters and return (with take) them as a single string. The return is there to stop further code from beeing run, and finish the current recursive call.

[6] If we come here, we have more digits to do. Get the first one.

[7] Take a copy of the input (with clone), so that the second part (starting at [11]) starts with the same values as first part (here).

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

[8] The digit «0» (zero) by itself is illegal, so we can abort this recursive call. (This will also stop things like «01», which is fine.)

[9] Add the new (single digit) number to the done list.

[10] Recursively go on.

[11] Any more elements? (So that we can do two digits.)

[12] Get the next digit, and add it to the current one.

[13] Numbers higher than 25 do not work out.

[14] Add the new (two-digit) number to the done list.

[15] Recursively go on.

Running it:

$ ./decoded-list 11
AA, K

$ ./decoded-list 1115
AAAE, AAO, AKE, KAE, KO

$ ./decoded-list 127
ABG, LG

Looking good.

With verbose mode:

$ ./decoded-list -v 11
:Loop:  + 1
:Loop: 1 + 1
:Take: 1 1 -> AA
:Loop:  + 11
:Take: 11 -> K
AA, K

$ ./decoded-list -v 1115
:Loop:  + 1
:Loop: 1 + 1
:Loop: 1 1 + 1
:Loop: 1 1 1 + 5
:Take: 1 1 1 5 -> AAAE
:Loop: 1 1 + 15
:Take: 1 1 15 -> AAO
:Loop: 1 + 11
:Loop: 1 11 + 5
:Take: 1 11 5 -> AKE
:Loop:  + 11
:Loop: 11 + 1
:Loop: 11 1 + 5
:Take: 11 1 5 -> KAE
:Loop: 11 + 15
:Take: 11 15 -> KO
AAAE, AAO, AKE, KAE, KO

$ ./decoded-list -v 127
:Loop:  + 1
:Loop: 1 + 2
:Loop: 1 2 + 7
:Take: 1 2 7 -> ABG
:Loop:  + 12
:Loop: 12 + 7
:Take: 12 7 -> LG
ABG, LG

And that's it.