Perl Weekly Challenge: Week 311

Challenge 1:

Upper Lower

You are given a string consists of english letters only.

Write a script to convert lower case to upper and upper case to lower in the given string.

Example 1
Input: $str = "pERl"
Output: "PerL"
Example 2
Input: $str = "rakU"
Output: "RAKu"
Example 3
Input: $str = "PyThOn"
Output: "pYtHoN"

This time I'm going to start with Perl as we can solve this challenge with one regularexpression.

$_ = shift; s/(.)/uc $1 eq $1 ? lc $1: uc $1/ge; say

(Full code on Github.)

We get the input from the command-line. The first command-line argument is removed with shift() and placed into $_. The s/// operator works on $_. In the first part, a character in $_ is matched and captured. The /g or global flag to s/// makes the capture occur for every character. Strictly speaking, I only needed to match letters but changing the case of non-letter characters is harmless and making the regular' expression stricter would only have made it longer for no good reason so I stuck with .. The /e flag allows us to execute code in the second half of s///. There we use the ternary operator, ?: to test if a captured character is upper-case by testing it for equality with a known upper-case version of it created with uc(). If it is uppercase, the character is converted to lower-case with lc(). If it is not, i.e if it is lower-case, it is converted to upper-case. The results of the substitution are output with say which also operates on $_.

The Raku version is just a little bit longer. It uses .subst(), the method version of s/// to join all steps into one statement.

@*ARGS.shift.subst(/(.)/, { $0.uc eq $0 ?? $0.lc !! $0.uc }, :g).say

(Full code on Github.)

Challenge 2:

Group Digit Sum

You are given a string, $str, made up of digits, and an integer, $int, which is less than the length of the given string.

Write a script to divide the given string into consecutive groups of size $int (plus one for leftovers if any). Then sum the digits of each group, and concatenate all group sums to create a new string. If the length of the new string is less than or equal to the given integer then return the new string, otherwise continue the process.

Example 1
Input: $str = "111122333", $int = 3
Output: "359"

Step 1: "111", "122", "333" => "359"
Example 2
Input: $str = "1222312", $int = 2
Output: "76"

Step 1: "12", "22", "31", "2" => "3442"
Step 2: "34", "42" => "76"
Example 3
Input: $str = "100012121001", $int = 4
Output: "162"

Step 1: "1000", "1212", "1001" => "162"

The first step in my Raku solution is assigning $str to a new variable. This is going to be repeatedly modified which we can't do to $str because, as a function argument, it is immutable.

my $sum = $str;

Now while the lenth of $sum is greater than $int...

while $sum.chars > $int {

...we split $sum into groups of $Int characters with .comb(). For some reason $int kept being interpreted as a String so I had to specifically cast it to an Int with .Int(). Using .map() each of these groups are in turn split into individual digits and added together with .comb() and .sum() All those summed groups are then concatenated together with .join(). The result is then assigned back to $sum.

    $sum = $sum.comb($int.Int).map({ $_.comb.sum }).join;
}

When $sum has shrunk to the required length, we print it out with say().

say $sum;

(Full code on Github.)

This is the Perl version.

my $sum = $str;

while (length $sum > $int) {

We have to provide our own sum() function and workaround the lack of .comb($int) by using a regular expression.

    $sum = join q{},  map { sum( split // ) } ($sum =~ /(\d{1, $int})/g);
}

say $sum;

(Full code on Github.)