I haven’t been feeling like blogging lately. In November, my mother, the woman who taught me how to program and was for decades my default person to ask complex SQL questions, went into the hospital because she was having trouble breathing. In early January, she died.
I’ve been struggling to deal with my feelings about this, but one of the things that I knew deep in my heart is that she wanted me to return to Perl blogging. If for no other reason than to honor her…
And with the first task being “circular”, I couldn’t help think about Joni Mitchell’s Circle Game. My mother loved folk music, too.
So let’s circle round to Perl Weekly Challenge 316.
Task 1: Circular
You are given a list of words.
Write a script to find out whether the last character of each word is the first character of the following word.
Example 1
Input: @list = ("perl", "loves", "scala")
Output: true
Example 2
Input: @list = ("love", "the", "programming")
Output: false
Example 3
Input: @list = ("java", "awk", "kotlin", "node.js")
Output: true
Approach
This is a pretty simple problem: loop through the list, grabbing the last character of the first element and the first character of the second element, and then compare them. If they don’t match, return false immediately. If we get through the entire list (well, getting the list down to 1 element remaining) without returning false, return true.
Raku
To be honest, I haven’t touched Raku in months, so I had to get my brain back in the swing of the language. I did remember that .comb
was used to split strings, and I remembered that the last element of a list was [*-1]
, not [-1]
like it is in Perl.
sub circular(@list) {
while (@list.elems > 1) {
# get the last letter of the first element
my $last = ( @list[0].comb )[*-1];
# and the first letter of the second element
my $first = ( @list[1].comb )[0];
# if they don't match case-insensitively, return false
return False if $last.lc ne $first.lc;
# remove the first element from the list
@list.pop;
}
return True; # we got through all the elements
}
View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
Example 1:
Input: @arr = ("perl", "loves", "scala")
Output: True
Example 2:
Input: @arr = ("love", "the", "programming")
Output: False
Example 3:
Input: @arr = ("java", "awk", "kotlin", "node.js")
Output: True
Perl
Having written the Raku solution, the Perl solution was trivial. All I had to do was replace the .comb
with the appropriate split
command, sub the [*-1]
for the old, familiar [-1]
, change the .lc
and .pop
from postfix method calls to Perl function calls, and voilá! Perl code.
sub circular(@list) {
while (@list > 1) {
# get the last letter of the first element
my $last = ( split //, $list[0] )[-1];
# and the first letter of the second element
my $first = ( split //, $list[1] )[0];
# if they don't match case-insensitively, return false
return 'False' if lc($last) ne lc($first);
# remove the first element from the list
pop @list;
}
return 'True'; # we got through all the elements
}
View the entire Perl script for this task on GitHub.
Python
Python was a little bit more of a challenge, since it’s less like Perl than Raku is, but I quickly remembered what I needed to remember.
def circular(arr):
while (len(arr) > 1):
# get the last letter of the first element
last = ( arr[0] )[-1]
# and the first letter of the second element
first = ( arr[1] )[0]
# if they don't match case-insensitively, return false
if last.lower() != first.lower():
return False
# remove the first element from the list
arr.pop(0)
return True; # we got through all the elements
View the entire Python script for this task on GitHub.
Elixir
The one thing I remembered about Elixir was recursion. Also that you need to return a value from every statement
def circular(list) when length(list) <= 1, do: true
def circular([one | list]) do
# get the second element from the list
two = Enum.at(list, 0)
# get the last letter of the first element
last = String.last(one)
# and the first letter of the second element
first = String.first(two)
# if they don't match case-insensitively, return false
if (String.downcase(last) != String.downcase(first)) do
false
else
# process the list without the first element
circular(list)
end
end
Then I remembered the |>
operator!
def circular(list) when length(list) <= 1, do: true
def circular([one | list]) do
# get the last letter of the first element
last = one |> String.last
# and the first letter of the second element
first = Enum.at(list, 0) |> String.first
# if they don't match case-insensitively, return false
if (String.downcase(last) != String.downcase(first)) do
false
else
# process the list without the first element
circular(list)
end
end
View the entire Elixir script for this task on GitHub.
Task 2: Subsequence
You are given two string.
Write a script to find out if one string is a subsequence of another.
A subsequence of a string is a new string that is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters.
Example 1
Input: $str1 = "uvw", $str2 = "bcudvew"
Output: true
Example 2
Input: $str1 = "aec", $str2 = "abcde"
Output: false
Example 3
Input: $str1 = "sip", $str2 = "javascript"
Output: true
Approach
At first, my thoughts went to something using regular expressions, but then I thought about it a little more and realized this is just a loop over both strings. Start with the first character of the first string. Start searching for that character in the second string. If we don’t find it, return failure. If we do find it, then get the next character from the first string, and resume searching the second string at the next character, and so on until we’ve made a single pass through both strings.
Raku
sub subsequence($str1, $str2) {
# start at the beginning of both strings
my ($p1, $p2) = (0, 0);
while ($p2 < $str2.chars) {
# if the current character of the first string matches
# the current character of the second string...
if (substr($str1, $p1, 1) eq substr($str2, $p2, 1)) {
# move to the next character in the first string
$p1++;
}
# if we've run off the end of the first string,
# we wound up finding all the characters!
return True if $p1 == $str1.chars;
# move to the next character of the second string
$p2++;
}
# if we've run off the end of the second string,
# this character isn't in the second string
return False;
}
View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input: $str1 = "uvw", $str2 = "bcudvew"
Output: True
Example 2:
Input: $str1 = "aec", $str2 = "abcde"
Output: False
Example 3:
Input: $str1 = "sip", $str2 = "javascript"
Output: True
Perl
Literally, the only changes from Raku to Perl were swapping .chars
for length()
and returning true and false as strings instead of boolean constants.
sub subsequence($str1, $str2) {
# start at the beginning of both strings
my ($p1, $p2) = (0, 0);
while ($p2 < length($str2)) {
# if the current character of the first string matches
# the current character of the second string...
if (substr($str1, $p1, 1) eq substr($str2, $p2, 1)) {
# move to the next character in the first string
$p1++;
}
# if we've run off the end of the first string,
# we wound up finding all the characters!
return 'True' if $p1 == length($str1);
# move to the next character of the second string
$p2++;
}
# if we've run off the end of the second string,
# this character isn't in the second string
return 'False';
}
View the entire Perl script for this task on GitHub.
Python
And, as usual, there isn’t much difference between the Perl-ish and Pythonic implementations of my approach.
def subsequence(str1, str2):
# start at the beginning of both strings
p1 = 0
p2 = 0
while (p2 < len(str2)):
# if the current character of the first string matches
# the current character of the second string...
if (str1[p1] == str2[p2]):
# move to the next character in the first string
p1 += 1
# if we've run off the end of the first string,
# we wound up finding all the characters!
if p1 == len(str1):
return True
# move to the next character of the second string
p2 += 1
# if we've run off the end of the second string,
# this character isn't in the second string
return False
View the entire Python script for this task on GitHub.
Elixir
Once again, the recursion encouraged by Elixir does all the heavy lifting. I just had to think about how to arrange the code so the flow of function calls wound up returning the results we want.
# if we've run off the end of the second string,
# this character isn't in the second string
def subsequence(_str1, str2, _p1, p2) when p2 >= byte_size(str2),
do: false
# if we've run off the end of the first string,
# we wound up finding all the characters!
def subsequence(str1, _str2, p1, _p2) when p1 >= byte_size(str1),
do: true
def subsequence(str1, str2, p1, p2) do
# if the current character of the first string matches
# the current character of the second string...
if String.at(str1, p1) == String.at(str2, p2) do
# move to the next character in the first string
subsequence(str1, str2, p1+1, p2)
else
# move to the next character of the second string
subsequence(str1, str2, p1, p2+1)
end
end
def subsequence(str1, str2) do
# start at the beginning of both strings
subsequence(str1, str2, 0, 0)
end
View 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-316/packy-anderson