The Find
with Raku

by Arne Sommer

The Find with Raku

[337] Published 5. April 2025.

This is my response to The Weekly Challenge #315.

Challenge #315.1: Find Words

You are given a list of words and a character.

Write a script to return the index of word in the list where you find the given character.

Example 1:
Input: @list = ("the", "weekly", "challenge")
       $char = "e"
Output: (0, 1, 2)
Example 2:
Input: @list = ("perl", "raku", "python")
       $char = "p"
Output: (0, 2)
Example 3:
Input: @list = ("abc", "def", "bbb", "bcd")
       $char = "b"
Output: (0, 2, 3)
File: find-words
#! /usr/bin/env raku

unit sub MAIN ($char  where $char ~~ /^<[a..z A..Z]>$/,   # [1]
               *@list where @list.elems > 0,              # [2]
               :v(:$verbose));

my @matches;                                              # [3]

for ^@list.elems -> $index                                # [4]
{
  my $match = @list[$index] ~~ /$char/;                   # [5]

  say ": Index $index Word: @list[$index] { $match ?? " - match" !! "" }"
    if $verbose;

  @matches.push: $index if $match;                        # [6]
}

say "({ @matches.join(", ") })";                          # [7]

[1] The first positional argument is the single character, enforced as an english letter - so that we do not have to worry about special characetrs in the regex in [5].

[2] Followed by a slurpy array for the words, with at least one element.

[3] The indices of matching words will end up here.

[4] Iterate over the indices in the array of words.

[5] Look for the character in the word, using a regex.

[6] Add the word index to the list of matches, if we have a match.

[7] Pretty print the result.

Running it:

$ ./find-words ae the weekly challenge
(0, 1, 2)

$ ./find-words p perl raku python
(0, 2)

$ ./find-words b abc def bbb bcd
(0, 2, 3)

Looking good.

With verbose mode:

$ ./find-words -v e the weekly challenge
: Index 0 Word: the - match
: Index 1 Word: weekly - match
: Index 2 Word: challenge - match
(0, 1, 2)

$ ./find-words -v p perl raku python
: Index 0 Word: perl  - match
: Index 1 Word: raku 
: Index 2 Word: python  - match
(0, 2)

$ ./find-words -v b abc def bbb bcd
: Index 0 Word: abc  - match
: Index 1 Word: def 
: Index 2 Word: bbb  - match
: Index 3 Word: bcd  - match
(0, 2, 3)

Challenge #315.2: Find Third

You are given a sentence and two words.

Write a script to return all words in the given sentence that appear in sequence to the given two words.

Example 1:
Input: $sentence = "Perl is a my favourite language but Python is my \
  favourite too."
       $first = "my"
       $second = "favourite"
Output: ("language", "too")
Example 2:
Input: $sentence = "Barbie is a beautiful doll also also a beautiful \
  princess."
       $first = "a"
       $second = "beautiful"
Output: ("doll", "princess")
Example 3:
Input: $sentence = "we will we will rock you rock you.",
       $first = "we"
       $second = "will"
Output: ("we", "rock")
File: find-thirds
#! /usr/bin/env raku

subset WORD where /^<[a..z A..Z]>+$/;                     # [1]
subset WORDS where /^<[a..z A..Z \s \. \, \- \! \;]>+$/;  # [2]

unit sub MAIN (WORDS $sentence;                           # [2a]
               WORD :f(:$first),                          # [1a]
               WORD :s(:$second));                        # [1b]

my @sentence   = $sentence.words;                         # [3]
my $first-word = @sentence.shift;                         # [4]
my @result;                                               # [5]

while @sentence.elems                                     # [6]
{
  if $first-word ne $first                                # [7]
  {
    $first-word = @sentence.shift;                        # [7a]
  }
  else                                                    # [8]
  {	
    my $second-word = @sentence.shift || last;            # [9]

    @result.push: @sentence[0]
      if $second-word eq $second && @sentence.elems;      # [10]

    $first-word = $second-word;                           # [11]
  }
}

say "({ @result.join(", ") })";                           # [12]

[1] A custom type, set up with subset for one word., used on the first [1a] and second [1b] words - which we specify as named parameters. Note that we allow the 26 basic english letters only, both lower- and uppercase.

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

[2] A second custom type for the sentence. We allow spaces, periods, commas, dashes, exclamation points and semicolons in addition to the letters of the words, in this string [2a].

The examples only uses space and period, but we should perhaps support some more characters - as done here.

[3] Split the sentence into words, with words.

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

[4] Get the first word from the sentence.

[5] The result (the third words) will end up here.

[6] As long as we are not finished with the sentence.

[7] Is the first word from the sentence the wrong one? If so, try with the next one from the sentence [7a].

[8] The first word is the correct one.

[9] Get the next word, or terminate the loop if that failed (i.e. no more words in the sentence).

[10] Add the third word to the result, if the second words match - and we have a third word.

[11] Prepare for the next iteration, where the current second word is the new first one.

[12] Pretty print the result.

Running it:

$ ./find-thirds -f=my -s=favourite "Perl is a my favourite language but \
  Python is my favourite too."
(language, too.)

$ ./find-thirds -f=a -s=beautiful "Barbie is a beautiful doll also also \
  a beautiful princess."
(doll, princess.)

$ ./find-thirds -f=we -s=will "we will we will rock you rock you."
(we, rock)

Looking good. Except for the trailing periods.

Let us fix that, by supplementing words with some custom magic.

File: find-thirds-fixed
#! /usr/bin/env raku

subset WORD where /^<[a..z A..Z]>+$/;
subset WORDS where /^<[a..z A..Z \s \. \, \- \! \;]>+$/;

unit sub MAIN (WORDS $sentence;
               WORD :f(:$first),
               WORD :s(:$second));

my @sentence   = $sentence.words>>.&de-punctuate;             # [1]
my $first-word = @sentence.shift;
my @result;

while @sentence.elems
{
  if $first-word ne $first
  {
    $first-word = @sentence.shift || last;
  }
  else
  {
    my $second-word = @sentence.shift || last;

    @result.push: @sentence[0] if $second-word eq $second && @sentence.elems;

    $first-word = $second-word;
  }
}

say "({ @result.join(", ") })";

sub de-punctuate ($word is copy)                              # [2]
{
  $word = $word.substr(0, $word.chars -2)                     # [3]
    while $word.substr($word.chars -1) eq any(',', '.', '-');

  return $word;
}

[1] Apply the «de-punctuate» procedure on each element in the array, using the hyper operator >> in combination with the handy .& «function-as-a-method» syntax.

See docs.raku.org/language/operators#index-entry-hyper… for more information about Hyper Operators and >>.

See docs.raku.org/language/operators#methodop_.& for more information about the special procedure invocation syntax .&.

[2] The is copy trait allows us to change the local copy.

See docs.raku.org/type/Parameter#method_copy for more information about is copy.

[3] Remove the very last character from the word, one by one, as long as it is one of the specified (punctuation) characters.

Running it gives the expected result:

$ ./find-thirds-fixed -f=my -s=favourite "Perl is a my favourite language \
  but Python is my favourite too."
(language, to)

$ ./find-thirds-fixed -f=a -s=beautiful "Barbie is a beautiful doll also \
  also a beautiful princess."
(doll, princes)

$ ./find-thirds-fixed -f=we -s=will "we will we will rock you rock you."
(we, rock)

Add as many punctuation characters as you want:

$ ./find-thirds-fixed -f=we -s=will "we will we will rock you rock \
  you................"
(we, rock)

And that's it.