Sort of Equal
with Raku

by Arne Sommer

Sort of Equal with Raku

[336] Published 24. March 2025.

This is my response to The Weekly Challenge #314.

Challenge #314.1: Equal Strings

You are given three strings.

You are allowed to remove the rightmost character of a string to make all equals.

Write a script to return the number of operations to make it equal otherwise -1.

Example 1:
Input: $s1 = "abc", $s2 = "abb", $s3 = "ab"
Output: 2

Operation 1: Delete "c" from the string "abc"
Operation 2: Delete "b" from the string "abb"
Example 2:
Input: $s1 = "ayz", $s2 = "cyz", $s3 = "xyz"
Output: -1
Example 3:
Input: $s1 = "yza", $s2 = "yzb", $s3 = "yzc"
Output: 3
File: equal-strings
#! /usr/bin/env raku

unit sub MAIN ($s1 where $s1.chars > 0,                              # [1]
               $s2 where $s2.chars > 0,                              # [1a]
               $s3 where $s3.chars > 0,                              # [1b]
               :v(:$verbose));

my @length = ($s1.chars, $s2.chars, $s3.chars);                      # [2]
my $min    = @length.min;                                            # [2a]
my $max    = @length.max;                                            # [2b]

if $min == $max                                                      # [3]
{
  if ($s1 eq $s2 eq $s3)                                             # [4]
  {
    say ": Same lenght, and all are equal" if $verbose;
    say '0';                                                         # [4a]
  }
  elsif $s1.substr(0,$min -1) eq $s2.substr(0,$min -1)
    eq $s3.substr(0,$min -1)                                         # [5]
  {
   say ": Same length, and all are equal when we drop the last character"
     if $verbose;
    say '3';                                                         # [5a]
  }
  else                                                               # [6]
  {
    say ": Same length, but they are not equal" if $verbose;
    say '-1';                                                        # [6a]
  }
}
elsif $max - $min > 1                                                # [7]
{
  say ": Longest string is more than 1 character longer than the shortest"
    if $verbose; 
  say '-1';                                                          # [7a]                          
}
elsif $s1.substr(0,$min) eq $s2.substr(0,$min) eq $s3.substr(0,$min) # [8]
{
  say ": Different length, but equal when we drop the last character in \
    the long ones" if $verbose;
  say @length.sum - ($min * 3);                                      # [8a]
}
else                                                                 # [9]
{
  say ": Different length (min = max-1), dropping off 1 character does \
    not help" if $verbose;
  say '-1';                                                          # [9a]
}

[1] The first [1], second [1a], and third [1b] strings, all of which must have at least one character.

[2] Get the length of the three strings [2], then the minimun length [2a] with min, and the maximum length [2b] with max.

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

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

[3] Do all the string have the same length?

[4] Are the three strings equal? Note the stacked eq operators. If so, we are done (without any operations).

[5] Are the three strings equal if we disregard the last character in each one? If so, print "3" [5a].

[6] The same length, but not [4] or [5], then they differ. Print "-1" [6a].

[7] Is the max length more than one longer than the minimum length? If so, print "-1" as we cannot chop off more than 1 character.

[8] The max length is one more than the min length when we come here. Are the strings equal when we drop the last character in the long one(s)? If so print the number of long strings (operations to perform).

[9] If we get here, the strings differ. print "-1".

Running it:

$ ./equal-strings abc abb ab
2

$ ./equal-strings ayz cyz xyz
-1

$ ./equal-strings yza yzb yzc
3

Looking good.

With verbose mode:

$ ./equal-strings -v abc abb ab
: Different length, but equal when we drop the last character in the long ones
2

$ ./equal-strings -v ayz cyz xyz
: Same length, but they are not equal
-1

$ ./equal-strings -v yza yzb yzc
: Same length, and all are equal when we drop the last character
3

Challenge #314.2: Sort Column

You are given a list of strings of same length.

Write a script to make each column sorted lexicographically by deleting any non sorted columns.

Return the total columns deleted.

Example 1:
Input: @list = ("swpc", "tyad", "azbe")
Output: 2

swpc
tyad
azbe

Column 1: "s", "t", "a" => non sorted
Column 2: "w", "y", "z" => sorted
Column 3: "p", "a", "b" => non sorted
Column 4: "c", "d", "e" => sorted

Total columns to delete to make it sorted lexicographically.
Example 2:
Input: @list = ("cba", "daf", "ghi")
Output: 1
Example 3:
Input: @list = ("a", "b", "c")
Output: 0

This challenge can be divided in two parts:

  • Get the column strings
  • Get rid on unsorted column strings

File: sort-column
#! /usr/bin/env raku

unit sub MAIN (*@list where @list.elems > 1 && ( [==] @list>>.chars ),  # [1]
               :v(:$verbose));

my $cols = @list[0].chars;                                              # [2]

my @cols;                                                               # [3]

for @list -> $string                                                    # [4]
{
  for ^$cols -> $index                                                  # [4a]
  {
    @cols[$index] ~= $string.substr($index, 1);                         # [4b]
  }
}

say ": Columns: { @cols.raku }" if $verbose;

my $deleted = 0;                                                        # [5]

for @cols -> $column                                                    # [6]
{
  my $sorted = $column.comb.sort.join;                                  # [7]

  print ": Column: " if $verbose;

  if $column ne $sorted                                                 # [8]
  {
    $deleted++;                                                         # [8a]
    say " => non sorted" if $verbose;
  }
  elsif $verbose
  {
    say " => sorted";
  }
}

say $deleted;                                                           # [9]

[1] A slurpy array for the list, with at least 2 elements. The elements must have the same length (number of characters). We use == in combination with the Reduction Metaoperator [] to ensure that they are all equal.

See docs.raku.org/language/operators#Reduction_metaoperators for more information about the Reduction Metaoperator [].

[2] Get the number of columns.

[3] The partial result, the columns (the first bullet point), will end up here.

[4] Iterate over the strings, and then the indices (of the characters in the string) [4a]. Append the character at that position in the string to the correct column string [4b].

[5] Number of deleted columns.

[6] Iterate over the columns.

[7] Get the sorted version of the column string. First comb to split the string into an array of characters, then sort to sort the characters (by the Unicode codepoint), and finally join to glue the characters into a string again.

[8] Do the column string and the sorted version differ? If so, delete the column (or rather, count it as deleted).

[9] Print the deleted number of columns.

Running it:

$ ./sort-column swpc tyad azbe
2

$ ./sort-column cba daf ghi
1

$ ./sort-column a b c
0

Looking good.

With verbose mode:

$ ./sort-column -v swpc tyad azbe
: Columns: ["sta", "wyz", "pab", "cde"]
: Column:  => non sorted
: Column:  => sorted
: Column:  => non sorted
: Column:  => sorted
2

$ ./sort-column -v cba daf ghi
: Columns: ["cdg", "bah", "afi"]
: Column:  => sorted
: Column:  => non sorted
: Column:  => sorted
1

$ ./sort-column -v a b c
: Columns: ["abc"]
: Column:  => sorted
0

And that's it.