Reverse Postition
with Raku

by Arne Sommer

Reverse Position with Raku

[340] Published 24. April 2025.

This is my response to The Weekly Challenge #318.

Challenge #318.1: Group Position

You are given a string of lowercase letters.

Write a script to find the position of all groups in the given string. Three or more consecutive letters form a group. Return "" if none found.

Example 1:
Input: $str = "abccccd"
Output: "cccc"
Example 2:
Input: $str = "aaabcddddeefff"
Output: "aaa", "dddd", "fff"
Example 3:
Input: $str = "abcdd"
Output: ""

Note that the challenge asks for the position of the groups, but the examples give the groups themselves. I'll follow the examples.

File: group-position
#! /usr/bin/env raku

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

sub grouped ($str, :$verbose)                       # [2]
{
  my @parts = gather
  {
    my $curr;
    my $count = 0;
    
    for $str.comb -> $char
    {
      if $count && $curr ne $char
      {
        take $curr x $count;
	$count = 0;
      }
      $curr = $char;
      $count++;
    }
    take $curr x $count;
  } 

  say ": Grouped '$str' as { @parts.raku }" if $verbose;
  return @parts;
}

say grouped($str, :$verbose).grep(*.chars > 2).map('"' ~ * ~ '"').join(", ") || '""';
  ## 3 ##################### # 4 ############# # 5 ############### # 6 ##### # 7 ###      

[1] Lowercase english letters only, with at least one.

[2] This procedure has been copied from my "broken-keys" program in Challenge 313 Reverse Broken with Raku. I have changed the verbose flag to be a named argument passed to the procedure this time, instead of the former global variable.

[3] Call "grouped" to to do the grouping, including verbose mode.

[4] Only keep elements in the list that have more than two characters.

[5] Print quotes around the strings.

[6] Add commas between the strings.

[7] No elements? Print a pair of empty quotes.

Running it:

$ ./group-position abccccd
"cccc"

$ ./group-position aaabcddddeefff
"aaa", "dddd", "fff"

$ ./group-position abcdd
""

Looking good.

With verbose mode:

$ ./group-position -v abccccd
: Grouped 'abccccd' as ["a", "b", "cccc", "d"]
"cccc"

$ ./group-position -v aaabcddddeefff
: Grouped 'aaabcddddeefff' as ["aaa", "b", "c", "dddd", "ee", "fff"]
"aaa", "dddd", "fff"

$ ./group-position -v abcdd
: Grouped 'abcdd' as ["a", "b", "c", "dd"]
""

Challenge #318.2: Reverse Equals

You are given two arrays of integers, each containing the same elements as the other.

Write a script to return true if one array can be made to equal the other by reversing exactly one contiguous subarray.

Example 1:
Input: @source = (3, 2, 1, 4)
       @target = (1, 2, 3, 4)
Output: true

Reverse elements: 0-2
Example 2:
Input: @source = (1, 3, 4)
       @target = (4, 1, 3)
Output: false
Example 3:
Input: @source = (2)
       @target = (2)
Output: true
File: reverse-equals
#! /usr/bin/env raku

subset NUMWORD where * ~~ /^<[0..9 \s]>+$/;    # [1]

unit sub MAIN (NUMWORD $source,                 # [1a]
               NUMWORD $target,                 # [1b]
               :v(:$verbose));

my @source = $source.words;                     # [2]
my @target = $target.words;                     # [2a]

die "Not the same elements"
  unless @source.sort eqv @target.sort;         # [3]

if @source eqv @target                          # [4]
{
  say 'true';                                   # [4a]
  exit;                                         # [4b]
}

my $end = @target.end;                          # [5]

for ^$end -> $i                                 # [6]
{
  for $i + 1 .. $end -> $j                      # [7]
  {
    my @copy = @source.clone;                   # [8]
    @copy[$i .. $j] = @copy[$i .. $j].reverse;  # [9]

    say ": ({ @source.join(",") }) Reverse $i..$j -> ({ @copy.join(",") })"
      if $verbose;

    if @copy eqv @target                        # [10]
    {
      say 'true';                               # [10a]
      exit;                                     # [10b]
    }
  }
}

say 'false';                                    # [11]

[1] A custom type set up with subset for the two arguments ([1a] and [1b]), allowing digits and spaces only. We specify the two arrays as two space separated strings.

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

[2] Use words to split the string into an array of integers.

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

[3] Ensure that that the source and target have the same content, i.e. are identical after sorting them. Note the use of the equivalence operator eqv to check that the structures contain the same values.

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

[4] Are the source and target identical? If so, we can reverse a one-element subarray thus keeping it unchanged (as done in the third example). Success. Say so [4a] and be done [4b].

[5] The highest index in the array(s), gotten with end.

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

[6] Iterate over the starting position for all possible subarrays, from index 0 to one less than the end (with the upto operator ^).

See docs.raku.org/routine/^ for more information about the Upto Operator ^.

[7] Iterate over the ending positions. Starting at one after the starting position and continuing to the end.

[8] Get a copy of the source array with clone.

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

[9] Swap the subarray with the reversed version, using array slices.

Note that it is possible to optimize the program by skipping the swapping and reversal if the non-swapped part(s) are different from the target. This could typically be as last if ... and/or next if ... just after the two for statements (in [6] and [7]). It is anybody's guess if this would speed up the program. I fear not, for the short arrays given in the excmples, and choose to let sleeping dogs lie.

[10] Have we gotten the target? If so, say so [10a] and exit [10b].

[11] Failure to succeed means failure. Say so.

Running it:

$ ./reverse-equals "3 2 1 4" "1 2 3 4"
true

$ ./reverse-equals "1 3 4" "4 1 3"
false

$ ./reverse-equals 2 2
true

Looking good.

With verbose mode:

$ ./reverse-equals -v "3 2 1 4" "1 2 3 4"
: (3,2,1,4) Reverse 0..1 -> (2,3,1,4)
: (3,2,1,4) Reverse 0..2 -> (1,2,3,4)
true

$ ./reverse-equals -v "1 3 4" "4 1 3"
: (1,3,4) Reverse 0..1 -> (3,1,4)
: (1,3,4) Reverse 0..2 -> (4,3,1)
: (1,3,4) Reverse 1..2 -> (1,4,3)
false

$ ./reverse-equals -v 2 2
true

And that's it.