A Token Alphabet
with Raku

by Arne Sommer

A Token Alphabet with Raku

[389] Published 18. March 2026.

This is my response to The Weekly Challenge #365.

#365.1 Alphabet Index Digit Sum You are given a string $str consisting of lowercase English letters, and an integer $k.

Write a script to convert a lowercase string into numbers using alphabet positions (a=1 … z=26), concatenate them to form an integer, then compute the sum of its digits repeatedly $k times, returning the final value.

Example 1:
Input: $str = "abc", $k = 1
Output: 6

Conversion: a = 1, b = 2, c = 3 -> 123
Digit sum: 1 + 2 + 3 = 6
Example 2:
Input: $str = "az", $k = 2
Output: 9

Conversion: a = 1, z = 26 -> 126
1st sum: 1 + 2 + 6 = 9
2nd sum: 9
Example 3:
Input: $str = "cat", $k = 1
Output: 6

Conversion: c = 3, a = 1, t = 20 -> 3120
Digit sum: 3 + 1 + 2 + 0 = 6
Example 4:
Input: $str = "dog", $k = 2
Output: 8

Conversion: d = 4, o = 15, g = 7 -> 4157
1st sum: 4 + 1 + 5 + 7 = 17
2nd sum: 1 + 7 = 8
Example 5:
Input: $str = "perl", $k = 3
Output: 6

Conversion: p = 16, e = 5, r = 18, l = 12 -> 1651812
1st sum: 1 + 6 + 5 + 1 + 8 + 1 + 2 = 24
2nd sum: 2+4 = 6
3rd sum: 6

I prefer to use the hyphenated task names ($task-name.words.join("-").lc ) as names for my programs. Except when they are too long, which is a judgement call. This name is definitely too long, but my usual acronymification using the first letter in each word ($task-name.words>>.substr(0,1).join.lc ) does not seem fitting. So too long a name it is, this time.

File: alphabet-index-digit-sum
#! /usr/bin/env raku

unit sub MAIN ($str where $str ~~ /^ <[a..z]>+ $/,     # [1]
               UInt $k where $k > 0,                   # [2]
               :v(:$verbose));

my @digits = $str.comb.map({ $_.ord - 'a'.ord + 1 });  # [3]
my $sum    = @digits.join;                             # [4]

say ": Digits: { @digits.join(", ") } -> $sum" if $verbose;

for 1 .. $k -> $iteration                              # [5]
{
  @digits = $sum.comb;                                 # [6]
  $sum    = @digits.sum;                               # [7]

  say ": Iteration $iteration: { @digits.join(" + ") } = $sum" if $verbose;

  last if $sum < 10;                                   # [8]
}

say $sum;                                              # [9]

[1] The string, with lowercase English letters only. At least one character.

[2] The integer, which I insist must be positive.

[3] Split the string into individual characters with comb). Then we translate each of them into numbers (a =>1, b => 2, .. , z => 26) with ord inside a map.

See docs.raku.org/routine/ord for more information about ord.

[4] Join the values to a new string; the initial sum.

[5] As many iterations as requested.

[6] Get the individual digits.

[7] Get the sum of them with sum.

See docs.raku.org/routine/sum for more information about sum.

[8] Bail out if we have reduced the sum to a single digit, as additional iterations would only waste time.

[9] Print the sum.

Running it:

$ ./alphabet-index-digit-sum abc 1
6

$ ./alphabet-index-digit-sum az 2
9

$ ./alphabet-index-digit-sum cat 1
6

$ ./alphabet-index-digit-sum dog 2
8

$ ./alphabet-index-digit-sum perl 3
6

Looking good.

With verbose mode:

$ ./alphabet-index-digit-sum -v abc 1
: Digits: 1, 2, 3 -> 123
: Iteration 1: 1 + 2 + 3 = 6
6

$ ./alphabet-index-digit-sum -v az 2
: Digits: 1, 26 -> 126
: Iteration 1: 1 + 2 + 6 = 9
: Iteration 2: 9 = 9
9

$ ./alphabet-index-digit-sum -v cat 1
: Digits: 3, 1, 20 -> 3120
: Iteration 1: 3 + 1 + 2 + 0 = 6
6

$ ./alphabet-index-digit-sum -v dog 2
: Digits: 4, 15, 7 -> 4157
: Iteration 1: 4 + 1 + 5 + 7 = 17
: Iteration 2: 1 + 7 = 8
8

$ ./alphabet-index-digit-sum -v perl 3
: Digits: 16, 5, 18, 12 -> 1651812
: Iteration 1: 1 + 6 + 5 + 1 + 8 + 1 + 2 = 24
: Iteration 2: 2 + 4 = 6
: Iteration 3: 6 = 6
6

The [8] row prevents silliness like this from running along:

$ ./alphabet-index-digit-sum -v perl 100000000000000
: Digits: 16, 5, 18, 12 -> 1651812
: Iteration 1: 1 + 6 + 5 + 1 + 8 + 1 + 2 = 24
: Iteration 2: 2 + 4 = 6
6

A high $k value can be used to reduce the sum to a single digit, if that is the goal, without a run time penalty because of [8].

#365.2 Valid Token Counter You are given a sentence.

Write a script to split the given sentence into space-separated tokens and count how many are valid words. A token is valid if it contains no digits, has at most one hyphen surrounded by lowercase letters, and at most one punctuation mark (!, ., ,) appearing only at the end.

Example 1:
Input: $str = "cat and dog"
Output: 3

Tokens: "cat", "and", "dog"
Example 2:
Input: $str = "a-b c! d,e"
Output: 2

Tokens: "a-b", "c!", "d,e"
"a-b" -> valid (one hyphen between letters)
"c!"  -> valid (punctuation at end)
"d,e" -> invalid (punctuation not at end)
Example 3:
Input: $str = "hello-world! this is fun"
Output: 4

Tokens: "hello-world!", "this", "is", "fun"
All satisfy the rules.
Example 4:
Input: $str = "ab- cd-ef gh- ij!"
Output: 2

Tokens: "ab-", "cd-ef", "gh-", "ij!"
"ab-"   -> invalid (hyphen not surrounded by letters)
"cd-ef" -> valid
"gh-"   -> invalid
"ij!"   -> valid
Example 5:
Input: $str = "wow! a-b-c nice."
Output: 2

Tokens: "wow!", "a-b-c", "nice."
"wow!"  -> valid
"a-b-c" -> invalid (more than one hyphen)
"nice." -> valid

I have ordered the checks in a way that I feel is optimal, but the verbose output is slightly different from the challenge explanation(s) because of this. That does not really matter.

File: valid-token-counter
#! /usr/bin/env raku

unit sub MAIN ($str, :v(:$verbose));     # [1]

my $valid = 0;                           # [2]

for $str.words -> $word                  # [3]
{
  if $word ~~ /\d/                       # [4]
  {
    say ": $word -> invalid (digit)" if $verbose;
  }
  elsif $word ~~ /<[.,!]>./              # [5]
  {
    say ": $word -> invalid (punctuation not at end)" if $verbose;
  }
  elsif $word ~~ /"-" .* "-"/            # [6]
  {
    say ": $word -> invalid (more than one hyphen)" if $verbose;
  }
  elsif $word ~~ /<[a..z]> "-" <[a..z]>/ # [7]
  {
    say ": $word -> valid (one hyphen between letters)" if $verbose;
    $valid++;
  }
  elsif $word ~~ / "-" /                 # [8]
  {
    say ": $word -> invalid (hyphen not between letters)" if $verbose;
  }
  else                                   # [9]
  {
    say ": $word -> valid" if $verbose;
    $valid++;
  }
}

say $valid;	  

[1] No restrictions on the input string, so an empty string is ok here.

[2] The number of valid tokens will end up here.

[3] Iterate over the words.

[4] Ignore words containing at least one digit.

[5] Ignore words with at least one character after a punctuation character.

[6] Ignore words with at least two hyphens.

[7] Count the word if it has a letter immediately before and after an hyphen.

[8] Ignore any other word with a hyphen.

[9] Count the word, as it is legal.

Running it:

$ ./valid-token-counter "cat and dog"
3

$ ./valid-token-counter "a-b c! d,e"
2

$ ./valid-token-counter "hello-world! this is fun"
4

$ ./valid-token-counter "ab- cd-ef gh- ij!"
2

$ ./valid-token-counter "wow! a-b-c nice."
2

Looking good.

With verbose mode:

$ ./valid-token-counter -v "cat and dog"
: cat -> valid
: and -> valid
: dog -> valid
3

$ ./valid-token-counter -v "a-b c! d,e"
: a-b -> valid
: c! -> valid
: d,e -> invalid (punctuation not at end)
2

$ ./valid-token-counter -v "hello-world! this is fun"
: hello-world! -> valid (one hyphen between letters)
: this -> valid
: is -> valid
: fun -> valid
4

$ ./valid-token-counter -v "ab- cd-ef gh- ij!"
: ab- -> invalid (hyphen not between letters)
: cd-ef -> valid (one hyphen between letters)
: gh- -> invalid (hyphen not between letters)
: ij! -> valid
2

$ ./valid-token-counter -v "wow! a-b-c nice."
: wow! -> valid
: a-b-c -> invalid (more than one hyphen)
: nice. -> valid
2

Using given/when gives a more compact program, but the file size is higher due to the extra indentation.

File: valid-token-counter-given
#! /usr/bin/env raku

unit sub MAIN ($str, :v(:$verbose));

my $valid = 0;

for $str.words -> $word
{
  given $word      # [1]
  {
    when /\d/      # [2]
    {
      say ": $word -> invalid (digit)" if $verbose;
    }
    when /<[.,!]>./
    {
      say ": $word -> invalid (punctuation not at end)" if $verbose;
    }
    when /"-" .* "-"/
    {
      say ": $word -> invalid (more than one hyphen)" if $verbose;
    }
    when /<[a..z]> "-" <[a..z]>/
    {
      say ": $word -> valid (one hyphen between letters)" if $verbose;
      $valid++;
    }
    when / "-" /
    {
      say ": $word -> invalid (hyphen not between letters)" if $verbose;
    }
    default        # [3]
    {
      say ": $word -> valid" if $verbose;
      $valid++;
    }
  }
}
 
say $valid;

[1] Inspect the value with given.

See docs.raku.org/syntax/default when for more information about given/when/default.

[2] Smartmatch the value against a regex.

[3] The final else part of the if program corresponds with default.

Running it gives the expected result. Here is the fourth example:

$ ./valid-token-counter-given -v "ab- cd-ef gh- ij!"
: ab- -> invalid (hyphen not between letters)
: cd-ef -> valid (one hyphen between letters)
: gh- -> invalid (hyphen not between letters)
: ij! -> valid
2

And that's it.