The tasks for Perl Weekly Challenge 358 are “Max Str Value” and “Encrypted String”. I didn’t want to use “max” for my music hook again, so I went looking through my collection for “value”… and found It’s What You Value by George Harrison.
Task 1: Max Str Value
You are given an array of alphanumeric string, @strings.
Write a script to find the max value of alphanumeric string in the given array. The numeric representation of the string, if it comprises of digits only otherwise length of the string.
Example 1
Input: @strings = ("123", "45", "6")
Output: 123
"123" -> 123
"45" -> 45
"6" -> 6
Example 2
Input: @strings = ("abc", "de", "fghi")
Output: 4
"abc" -> 3
"de" -> 2
"fghi" -> 4
Example 3
Input: @strings = ("0012", "99", "a1b2c")
Output: 99
"0012" -> 12
"99" -> 99
"a1b2c" -> 5
Example 4
Input: @strings = ("x", "10", "xyz", "007")
Output: 10
"x" -> 1
"xyz" -> 3
"007" -> 7
"10" -> 10
Example 5
Input: @strings = ("hello123", "2026", "perl")
Output: 2026
"hello123" -> 8
"perl" -> 4
"2026" -> 2026
Approach
There may be a clever way to get this, but I’m just going to write a function that returns the “string value” of a string: if a regular expression finds no non-numeric digits, return the numeric representation of the string, otherwise return the length. Map the input list using this function, and then get the max value returned.
Raku
sub strVal($str) {
return $str.Int unless $str ~~ /\D/;
return $str.chars;
}
sub maxStrVal(@strings) {
return max @strings.map({ strVal($_) });
}View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
Example 1:
Input: @strings = ("123", "45", "6")
Output: 123
Example 2:
Input: @strings = ("abc", "de", "fghi")
Output: 4
Example 3:
Input: @strings = ("0012", "99", "a1b2c")
Output: 99
Example 4:
Input: @strings = ("x", "10", "xyz", "007")
Output: 10
Example 5:
Input: @strings = ("hello123", "2026", "perl")
Output: 2026Perl
Because there’s no explicit integer type in Perl, I got to use the idiom of adding 0 to a variable to get the numeric representation of it.
use List::AllUtils qw( max );
sub strVal($str) {
return $str + 0 unless $str =~ /\D/;
return length($str);
}
sub maxStrVal(@strings) {
return max map { strVal($_) } @strings;
}View the entire Perl script for this task on GitHub.
Python
In Python, rather than a regular expression, I’m using str.isdigit().
def str_val(s):
return int(s) if s.isdigit() else len(s)
def max_str_val(strings):
return max(str_val(s) for s in strings)View the entire Python script for this task on GitHub.
Elixir
At first, I was going to use String.to_integer/1 to transform the numeric strings to integers:
def str_val(s) do
if Regex.match?(~r/\D/, s) do
String.length(s)
else
String.to_integer(s)
end
endbut then I noticed this:
If you want to parse a string that may contain an ill-formatted integer, use
Integer.parse/1.
That function returns a tuple in the form of {integer, remainder_of_binary}. if the string can be parsed as an integer, otherwise it returns :error. Using that, I could do my testing of the string and the conversion all in one step.
def str_val(s) do
case Integer.parse(s) do
{int, _} -> int
:error -> String.length(s)
end
end
def max_str_val(strings) do
strings
|> Enum.map(fn s -> str_val(s) end)
|> Enum.max
endView the entire Elixir script for this task on GitHub.
Task 2: Encrypted String
You are given a string $str and an integer $int.
Write a script to encrypt the string using the algorithm – for each character $char in $str, replace $char with the $int th character after $char in the alphabet, wrapping if needed and return the encrypted string.
Example 1
Input: $str = "abc", $int = 1
Output: "bcd"
Example 2
Input: $str = "xyz", $int = 2
Output: "zab"
Example 3
Input: $str = "abc", $int = 27
Output: "bcd"
Example 4
Input: $str = "hello", $int = 5
Output: "mjqqt"
Example 5
Input: $str = "perl", $int = 26
Output: "perl"
Approach
Even though I try to write the Raku solution before the Perl solution, I knew as soon as I saw this it was a job for Perl’s tr operator (or, as the docs call it, the Transliteration Quote-like Operator). Build a string of the alphabet, shift it by the required number of characters, and the use tr. I knew this because this problem is basically ROT13, and tr is the classic tool for ROT13.
Raku
Raku has a transliteration quote-like operator, but it’s also got one better: the .trans method on the Str class.
sub rotN($str, $n is copy) {
$n = $n % 26; # 0 <= n < 26
my $orig = ('a'..'z').join;
my $shifted = $orig.substr($n..26) ~ $orig.substr(0..$n-1);
return $str.trans($orig => $shifted);
}View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input: $str = "abc", $int = 1
Output: "bcd"
Example 2:
Input: $str = "xyz", $int = 2
Output: "zab"
Example 3:
Input: $str = "abc", $int = 27
Output: "bcd"
Example 4:
Input: $str = "hello", $int = 5
Output: "mjqqt"
Example 5:
Input: $str = "perl", $int = 26
Output: "perl"Perl
However, because I want my search and replacement lists to be dynamic, I have to jump through a small hoop:
Because the transliteration table is built at compile time, neither the SEARCHLIST nor the REPLACEMENTLIST are subjected to double quote interpolation. That means that if you want to use variables, you must use an
eval().
I also want to use the /r modifier:
If the
/r(non-destructive) option is present, a new copy of the string is made and its characters transliterated, and this copy is returned, instead of a count, no matter whether it was modified or not.
sub rotN($str, $n) {
$n = $n % 26; # 0 <= n < 26
my $orig = join '', 'a'..'z';
my $shifted = substr($orig, $n, 26) . substr($orig, 0, $n);
return eval "\$str =~ tr/$orig/$shifted/r";
}View the entire Perl script for this task on GitHub.
Python
In Python, I could generate the range of ascii codes from a to z with range(ord('a'), ord('z')+1), convert them to a list of characters with [chr(c) for c in , and the join that list into a string, but it’s much easier to just use the constant range(ord('a'), ord('z')+1)]ascii_lowercase in the string library.
To do the transliteration, I’m using str.translate and str.maketrans.
import string
def rot_n(s, n):
n = n % 26 # 0 <= n < 26
orig = string.ascii_lowercase
shifted = orig[n:] + orig[:n]
return s.translate(str.maketrans(orig, shifted))View the entire Python script for this task on GitHub.
Elixir
Elixir, on the other hand, doesn’t have a built-in transliteration method in any of the standard modules, so I needed to roll my own.
On line 20, I’m using ?a..?z to generate a range from 97 to 122, and then Enum.to_list/1 converts that to a list, Kernel.to_string/1 converts that to a string, and then I’m breaking that out to a list of characters using String.codepoints/1. By keeping the original a list, I’m able to use Enum.split/2 to break it into two lists and then rejoin it to make a shifted list, and I can pass both these to my hand-rolled translate function.
I’m not importing String.split/2 because if I do, I get function split/2 imported from both String and Enum, call is ambiguous.
import Enum
import String, except: [split: 2]
@doc """
Provide a transliteration method, since Elixir doesn't have one
"""
def translate(s, orig, shifted) do
charmap = zip_reduce([ orig, shifted ], %{},
fn [o,s], m -> Map.put(m, o, s) end
)
codepoints(s)
|> map(fn c -> Map.get(charmap, c) end)
|> join
end
def rot_n(s, n) do
n = rem(n, 26) # 0 <= n < 26
orig = ?a..?z |> to_list |> to_string |> codepoints
{s2, s1} = split(orig, n)
shifted = s1 ++ s2
translate(s, orig, shifted)
endView the entire Elixir script for this task on GitHub.
Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-358/packy-anderson