Perl Weekly Challenge 377‘s tasks are “Reverse Existence” and “Prefix Suffix”.
Hmmm. Reverse. What does “reverse” make me think of musically? The existence of this song.
Task 1: Reverse Existence
You are given a string.
Write a script to find whether any substring of length 2 is also present in the reverse of the given string.
Input: $str = "abcba"
Output: true
Reverse of given string is "abcba".
The substring "ab" in original string is also present in the reverse string too.
Input: $str = "racecar"
Output: true
The substring "ce" is present in both.
Input: $str = "abcd"
Output: false
Input: $str = "banana"
Output: true
The substring "an" is present in both.
Input: $str = "hello"
Output: true
The substring "ll" is present in both.
Approach
There’s probably a clever way to do this, but I’m just going to brute-force it: generate a reversed string, $trs, and then take all the two-character slices of $str and see if any are found in $trs. Worst case, for a string of length N, I’m doing N-1 lookups.
Raku
I really wanted to use an experimental extension of .comb that’s in the long-awaited Raku 6.e:
The
rotorpair indicates the number of characters to fetch as the key (the “size”), and the number of “steps” forward to take afterwards. Its main intended use is to provide a way to create N-grams from strings in an efficient manner. By default only strings of the specified size will be produced.
so I decided to use the v6.e.PREVIEW version. With that, "racecar".comb(2 => -1) yields the list ["ra", "ac", "ce", "ec", "ca", "ar"], making short work of generating two-character substrings. Raku’s index returns Nil if the substring isn’t found, so I use .defined to determine if the substring was found.
use v6.e.PREVIEW; # so I can use .comb with a rotor pair
sub reverseExistence($str) {
my $trs = $str.flip;
for $str.comb(2 => -1) -> $substr {
return 'true' if $trs.index($substr).defined;
}
return 'false';
}View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
Example 1:
Input: $str = "abcba"
Output: true
Example 2:
Input: $str = "racecar"
Output: true
Example 3:
Input: $str = "abcd"
Output: false
Example 4:
Input: $str = "banana"
Output: true
Example 5:
Input: $str = "hello"
Output: truePerl
Perl, however, doesn’t have that, so I just looped over starting character positions from 0 to the penultimate character of the string, and then used substr() to generate the substring. Perl’s index returns -1 if the substring isn’t found, so I use >= 0 to determine if the substring was found.
sub reverseExistence($str) {
my $trs = reverse $str;
foreach my $i ( 0..length($str)-2 ) {
my $substr = substr($str, $i, 2);
return 'true' if index($trs, $substr) >= 0;
}
return 'false';
}View the entire Perl script for this task on GitHub.
Python
As I learned back in PWC 256, the fastest and most idiomatic way to reverse a string in Python is string[::-1]. Also, being able to use in to test to see if a substring is in a string makes this implementation quite neat.
def reverse_existence(string):
gnirts = string[::-1]
for i in range(len(string)-1):
substr = string[i:i+2]
if substr in gnirts: return "true"
return "false"View the entire Python script for this task on GitHub.
Elixir
Rather than looping, I’m using a Range to produce numbers from 0 to length(str)-2, then piping that through Enum.reduce/3 to produce a list of 2-character substrings, and then piping that list into a reverse_existence/2 that recursively calls itself to check if the reversed string contains any of the substrings.
def reverse_existence([], _), do: "false"
def reverse_existence([substr | tail], rts) do
if String.contains?(rts, substr), do: "true",
else: reverse_existence(tail, rts)
end
def reverse_existence(str) do
0..String.length(str)-2
|> Enum.reduce([],
fn i, list -> list ++ [String.slice(str, i, 2)] end)
|> reverse_existence(String.reverse(str))
endView the entire Elixir script for this task on GitHub.
Task 2: Prefix Suffix
You are given an array of strings.
Write a script to find if the two strings (str1, str2) in the given array such that str1 is prefix and suffix of str2. Return the total count of such pairs.
Input: @array = ("a", "aba", "ababa", "aa")
Output: 4
$array[0], $array[1]: "a" is a prefix and suffix of "aba"
$array[0], $array[2]: "a" is a prefix and suffix of "ababa"
$array[0], $array[3]: "a" is a prefix and suffix of "aa"
$array[1], $array[2]: "aba" is a prefix and suffix of "ababa"
Input: @array = ("pa", "papa", "ma", "mama")
Output: 2
$array[0], $array[1]: "pa" is a prefix and suffix of "papa"
$array[2], $array[3]: "ma" is a prefix and suffix of "mama"
Input: @array = ("abao", "ab")
Output: 0
Input: @array = ("abab", "abab")
Output: 1
$array[0], $array[1]: "abab" is a prefix and suffix of "abab"
Input: @array = ("ab", "abab", "ababab")
Output: 3
$array[0], $array[1]: "ab" is a prefix and suffix of "abab"
$array[0], $array[2]: "ab" is a prefix and suffix of "ababab"
$array[1], $array[2]: "abab" is a prefix and suffix of "ababab"
Input: @array = ("abc", "def", "ghij")
Output: 0
Approach
This feels like a regex. We take str1, str2 and return true if str2 matches /^str1/ and /str1$/.
Raku
But why use a regex when Raku’s Str class has .starts-with and .ends-with methods? I could drop lines 6 and 14 if I didn’t also want to return the explanatory text.
sub prefixSuffix(@array) {
my ($count, $explain) = (0, "");
for 0 .. @array.end - 1 -> $i {
my $istr = @array[$i];
for $i+1 .. @array.end -> $j {
my $jstr = @array[$j];
next unless $jstr.starts-with($istr)
&& $jstr.ends-with($istr);
$count++;
$explain ~= qq{\n\$array[$i], \$array[$j]: "$istr" is }
~ qq{a prefix and suffix of "$jstr"};
}
}
return $count, $explain;
}View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input: @array = ("a", "aba", "ababa", "aa")
Output: 4
$array[0], $array[1]: "a" is a prefix and suffix of "aba"
$array[0], $array[2]: "a" is a prefix and suffix of "ababa"
$array[0], $array[3]: "a" is a prefix and suffix of "aa"
$array[1], $array[2]: "aba" is a prefix and suffix of "ababa"
Example 2:
Input: @array = ("pa", "papa", "ma", "mama")
Output: 2
$array[0], $array[1]: "pa" is a prefix and suffix of "papa"
$array[2], $array[3]: "ma" is a prefix and suffix of "mama"
Example 3:
Input: @array = ("abao", "ab")
Output: 0
Example 4:
Input: @array = ("abab", "abab")
Output: 1
$array[0], $array[1]: "abab" is a prefix and suffix of "abab"
Example 5:
Input: @array = ("ab", "abab", "ababab")
Output: 3
$array[0], $array[1]: "ab" is a prefix and suffix of "abab"
$array[0], $array[2]: "ab" is a prefix and suffix of "ababab"
$array[1], $array[2]: "abab" is a prefix and suffix of "ababab"
Example 6:
Input: @array = ("abc", "def", "ghij")
Output: 0Perl
And I know I said regex, but it occurred to me I could implement starts_with and ends_with in Perl using index and rindex, and I wouldn’t need to incur the overhead of the full regex engine.
sub starts_with($str, $substr) { index($str, $substr) == 0; }
sub ends_with($str, $substr) {
rindex($str, $substr) == length($str) - length($substr);
}
sub prefixSuffix(@array) {
my ($count, $explain) = (0, "");
for my $i ( 0 .. $#array - 1 ) {
my $istr = $array[$i];
for my $j ( $i+1 .. $#array ) {
my $jstr = $array[$j];
next unless starts_with($jstr, $istr)
&& ends_with($jstr, $istr);
$count++;
$explain .= qq{\n\$array[$i], \$array[$j]: "$istr" is }
. qq{a prefix and suffix of "$jstr"};
}
}
return $count, $explain;
}View the entire Perl script for this task on GitHub.
Python
Really, it looks like of the languages I’m using, only Perl didn’t have a built-in .startswith and .endswith methods.
def prefix_suffix(array):
count, explain = 0, ""
for i in range(len(array)-1):
istr = array[i]
for j in range(i+1, len(array)):
jstr = array[j]
if not jstr.startswith(istr): continue
if not jstr.endswith(istr): continue
count += 1
explain += f'\n$array[{i}], $array[{j}]: "{istr}" is '
explain += f'a prefix and suffix of "{jstr}"'
return count, explainView the entire Python script for this task on GitHub.
Elixir
Part of the heavy lifting in the Elixir solution is that, just like in Perl, the return value of a function is the value of the last expression in the function. In each of the Enum.reduce/3 calls, the anonymous function winds up evaluating the tuple {count, explain} last, so that’s the return value, which is, in turn, the return value of the reduce call.
def prefix_suffix(array) do
Enum.reduce(0..length(array)-2, {0, ""},
fn i, {count, explain} ->
Enum.reduce(i+1..length(array)-1, {count, explain},
fn j, {count, explain} ->
istr = Enum.at(array, i)
jstr = Enum.at(array, j)
cond do
String.starts_with?(jstr, istr) &&
String.ends_with?(jstr, istr) ->
{
count + 1,
explain <> "\n$array[#{i}], $array[#{j}]: "
<> "\"#{istr}\" is a prefix and suffix "
<> "of \"#{jstr}\""
}
true -> {count, explain}
end
end)
end)
endView the entire Elixir script for this task on GitHub.
Here’s all my solutions in GitHub: https://github.com/packy/perlweeklychallenge-club/tree/challenge-377-packy-anderson/challenge-377/packy-anderson