Perl Weekly Challenge: The Sequence Goes Round and Round…

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

Leave a Reply