The Last Buddy
with Raku

by Arne Sommer

The Last Buddy with Raku

[353] Published 23. July 2025.

This is my response to The Weekly Challenge #331.

Challenge #331.1: Last Word

You are given a string.

Write a script to find the length of last word in the given string.

Example 1:
Input: $str = "The Weekly Challenge"
Output: 9
Example 2:
Input: $str = "   Hello   World    "
Output: 5
Example 3:
Input: $str = "Let's begin the fun"
Output: 3
File: last-word
#! /usr/bin/env raku

unit sub MAIN ($str where $str.chars > 0,   # [1]
               :v(:$verbose));

say ": Last word: '{ $str.words.tail }'" if $verbose;

say $str.words.tail.chars;                  # [2]

[1] A string with at least one character. A short word, but I is a word. Grammatical pun intended.

[2] Split the string into a list of words (with words), pick the very last one (with tail), and print the length.

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

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

Running it:

$ ./last-word "The Weekly Challenge"
9

$ ./last-word "   Hello   World    "
5

$ ./last-word "Let's begin the fun"
3

Looking good.

With verbose mode:

$ ./last-word -v "The Weekly Challenge"
: Last word: 'Challenge'
9

$ ./last-word -v "   Hello   World    "
: Last word: 'World'
5

$ ./last-word -v "Let's begin the fun"
: Last word: 'fun'
3

Challenge #331.2: Buddy Strings

You are given two strings, source and target.

Write a script to find out if the given strings are Buddy Strings.

Example 1:
Input: $source = "fuck"
       $target = "fcuk"
Output: true

The swapping of 'u' with 'c' makes it buddy strings.
Example 2:
Input: $source = "love"
       $target = "love"
Output: false
Example 3:
Input: $source = "fodo"
       $target = "food"
Output: true
Example 4:
Input: $source = "feed"
       $target = "feed"
Output: true
File: buddy-strings
#! /usr/bin/env raku

unit sub MAIN ($source where $source.chars > 0,             # [1]
               $destination where $destination.chars > 0,   # [2]
               :v(:$verbose));

if $destination.chars != $source.chars                      # [3]
{
  say ": Different string lengths; { $source.chars } vs \
    { $destination.chars }" if $verbose;

  say False;                                                # [3a]
}
else
{
  my @pairs = $source.comb Z $destination.comb;             # [4]
  say ": Pairs: { @pairs.raku }" if $verbose;

  my @changes = @pairs.grep({ $_[0] ne $_[1] });            # [5]
  say ": Pairs with changes: { @changes.raku }" if $verbose;

  if @changes.elems == 2                                    # [6]
  {
    say @changes[0][0] eq @changes[1][1] &&
        @changes[1][1] eq @changes[0][0];                   # [7]
  }
  else
  {
    say ": Not two pairs of letters that differ, got { @changes.elems}"
      if $verbose;

    say False;                                              # [8]
  }
}

[1,2] The two strings, with at least 1 character in each.

[2] We could have changed the constraint to where $destination.chars == $source.chars, but the program should print False in that case - so we it in [3] instead.

[3] Different length of the two strings? If so, say False.

[4] Split the two strinmgs into into individual characters (with comb), and zip the two arrays together like a DNA Helix with the infix Z (zipper) operator. The result is an array consisting of subarrays with two elements, one each from source and destination.

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

[5] Use grep to keep only the elements (subarrays) where the two values differ.

[6] Have we changed exactly two characters?

[7] Have we swapped both of them with eachother? Print true if we have, and False if not.

[8] Not swapped exactly two characters, then print False as we need exactly 2 swappings.

Running it:

$ ./buddy-strings fuck fcuk
True

$ ./buddy-strings love love
False

$ ./buddy-strings -fodo food
True

$ ./buddy-strings feed feed
False

Looking good, except the last one. (So, no. Not actually looking good at all...)

Let us do it with verbose mode before looking into the problem:

$ ./buddy-strings -v fuck fcuk
: Pairs: [("f", "f"), ("u", "c"), ("c", "u"), ("k", "k")]
: Pairs with changes: [("u", "c"), ("c", "u")]
True

$ ./buddy-strings -v love love
: Pairs: [("l", "l"), ("o", "o"), ("v", "v"), ("e", "e")]
: Pairs with changes: []
: Not two pairs of letters that differ, got 0
False

$ ./buddy-strings -v fodo food
: Pairs: [("f", "f"), ("o", "o"), ("d", "o"), ("o", "d")]
: Pairs with changes: [("d", "o"), ("o", "d")]
True

$ ./buddy-strings -v feed feed
: Pairs: [("f", "f"), ("e", "e"), ("e", "e"), ("d", "d")]
: Pairs with changes: []
: Not two pairs of letters that differ, got 0
False

The idea is that we can swap the first «e» with the second «e». That is not so hard to program:

File: buddy-strings-ok
#! /usr/bin/env raku

unit sub MAIN ($source where $source.chars > 0,
               $destination where $destination.chars > 0,
               :v(:$verbose));

if $destination.chars != $source.chars
{
  say ": Different string lengths; { $source.chars } vs \
    { $destination.chars }" if $verbose;
  say False;
}
else
{
  my @pairs = $source.comb Z $destination.comb;
  say ": Pairs: { @pairs.raku }" if $verbose;

  my @changes = @pairs.grep({ $_[0] ne $_[1] });
  say ": Pairs with changes: { @changes.raku }" if $verbose;

  if @changes.elems == 2
  {
    say @changes[0][0] eq @changes[1][1] &&
        @changes[1][1] eq @changes[0][0];
  }
  elsif @changes.elems == 0 && $source.comb.repeated.elems    # [1]   
  {                                                                   
    say ": Identical strings, but we can swap duplicates" if $verbose;
    say True;                                                 # [1a]  
  }                                                                   
  else
  {
    say ": Not two pairs of letters that differ, got { @changes.elems }"
      if $verbose;

    say False;
  }
}

[1] No swappings (of different characters), but we have repeated characters (with repeated) in one of the strings (actually both, as they are equal). With at least one duplicate, we can swap the original one and that duplicate and succeed; print Trrue [1a].

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

Running it with verbose mode:

$ ./buddy-strings-ok -v fuck fcuk
: Pairs: [("f", "f"), ("u", "c"), ("c", "u"), ("k", "k")]
: Pairs with changes: [("u", "c"), ("c", "u")]
True

$ ./buddy-strings-ok -v love love
: Pairs: [("l", "l"), ("o", "o"), ("v", "v"), ("e", "e")]
: Pairs with changes: []
: Not two pairs of letters that differ, got 0
False

$ ./buddy-strings-ok -v fodo food
: Pairs: [("f", "f"), ("o", "o"), ("d", "o"), ("o", "d")]
: Pairs with changes: [("d", "o"), ("o", "d")]
True

$ ./buddy-strings-ok -v feed feed
: Pairs: [("f", "f"), ("e", "e"), ("e", "e"), ("d", "d")]
: Pairs with changes: []
: Identical strings, but we can swap duplicates
True

Some more, just for fun:

$ ./buddy-strings-ok -v aaaa aaaa
: Pairs: [("a", "a"), ("a", "a"), ("a", "a"), ("a", "a")]
: Pairs with changes: []
: Identical strings, but we can swap duplicates
True

$ ./buddy-strings-ok -v abcd abcd
: Pairs: [("a", "a"), ("b", "b"), ("c", "c"), ("d", "d")]
: Pairs with changes: []
: Not two pairs of letters that differ, got 0
False

And that's it.