DEV Community

Bob Lied
Bob Lied

Posted on

PWC 239 Adagio for frayed knots

Ah, strings, the inexhaustible cornucopia of programming tasks. I found this week's challenges easy, so I cast around for what else "strings" reminds me of. The first thing that came to mind was Samuel Barber's Adagio for Strings, which has been called the saddest music ever written. Take seven minutes to listen to it in a quiet place, and come back when you've stopped sobbing.

The other thing it reminded me of was a joke that, when I first heard it, I thought was the funniest joke ever. A string goes into a bar, and the bartender yells at him, "Get out! We don't serve your kind here!" So the string steps outside. He ties himself in a knot, ruffles up his ends, and steps back into the bar. The bartender says, "Hey, aren't you that string that was just here?" The string says, "No, I'm a frayed knot."

Now that I am older and wiser, I know that this is not actually the funniest joke ever. The funniest joke ever is this:

Q: What's brown and sticky?
A: A stick.

Thank you; thank you. I'll be here all week. Don't forget to tip your waiter.


Okay, let's solve those string problems. I thought of answers almost as soon as I read the problems, so I'm not going to belabor alternatives. These are the answers.

Task 1 Same String

You are given two arrays of strings.
Write a script to find out if the word created
by concatenating the array elements is the same.
Enter fullscreen mode Exit fullscreen mode

Example 1

Input: @arr1 = ("ab", "c"), @arr2 = ("a", "bc")
Output: true

Using @arr1, word1 => "ab" . "c" => "abc"
Using @arr2, word2 => "a" . "bc" => "abc"

Example 2

Input: arr1 = ("ab", "c"), @arr2 = ("ac", "b")
Output: false

Example 3

Input: @arr1 = ("ab", "cd", "e"), @arr2 = ("abcde")
Output: true

Solution

What's happening here is that the elements of @arr1 are being concatenated into one long string; the elements of @arr2 are being concatenated into another; and we want the result of comparing them.

There are two good ways of concatenating a list of strings. The first is to use join with an empty delimiter: join('', @arr1). That's a perfectly good solution, and for people who switch among languages, it's easy to read and remember because there's a similar function in a lot of programming languages, including at least Java, Javascript, Python, Ruby, Tcl, and PHP.

The other pretty good way is to use string interpolation. When an array is interpolated into a string in Perl, all the elements of the array are listed in order, with a separator given by the $" special variable. This is documented in perldoc perlvar, and in exhausting depth in the "Quote and quote-like operators" section of perldoc perlop.

Using string interpolation, we should localize $" so it only applies in the scope of the subroutine, not globally to the rest of the program:

sub sameString($arr1, $arr2)
{
    local $" = '';
    return "$arr1->@*" eq "$arr2->@*"; 
}
Enter fullscreen mode Exit fullscreen mode

What other Perl subtleties lurk here? Well, we want to pass two arrays into a subroutine. We're doing that by reference, because passing two lists as arguments of a function call doesn't work -- they get flattened into one long list. That means the subroutine has to be called like

sameString([ qw(ab c) ], [ qw(a bc) ] ); # Literal strings
sameString(\@arr1, \@arr2); # Using references to arrays
Enter fullscreen mode Exit fullscreen mode

I'm using post-fix array dereferencing. I find it more readable than the older @$arr1 syntax.

Task 2 Consistent Strings

You are given an array of strings and an
allowed string having distinct characters.

A string is consistent if all characters in
the string appear in the string allowed.

Write a script to return the number of
consistent strings in the given array.
Enter fullscreen mode Exit fullscreen mode

Example 1

Input: @str = ("ad", "bd", "aaab", "baa", "badab")
$allowed = "ab"
Output: 2

Strings "aaab" and "baa" are consistent since they only
contain characters 'a' and 'b'.

Example 2

Input: @str = ("a", "b", "c", "ab", "ac", "bc", "abc")
$allowed = "abc"
Output: 7

Example 3

Input: @str = ("cc", "acd", "b", "ba", "bac", "bad", "ac", "d")
$allowed = "cad"
Output: 4

Strings "cc", "acd", "ac", and "d" are consistent.

Regular expression match, done. The allowed characters form a character class, and we want a string that contains one or more of those characters between the beginning and end. Using grep in scalar mode returns a count.

sub consistentStrings($allowed, @str)
{
    return 0 if $allowed eq "";
    my $rx = qr/^[$allowed]+$/;

    return scalar grep /$rx/, @str;
}
Enter fullscreen mode Exit fullscreen mode

What's that first part about return 0? If $allowed could be an empty string, it would create /^[]$/, which is an invalid regular expression. That's a minimal sanity check, but more should be done. Once we open that can of worms, some interesting complications arise. What, exactly, can come between the [ and ] of a character class? Most metacharacters lose their special powers within brackets, but there are rules for ^, -, brackets, and braces, and Unicode named characters are still allowed. $allowed could even look like .]anything could go here[. It might be best to sanitize the $allowed string and limit it to only alphabetic characters before we use it.

In this subroutine, I didn't use array references. By making the $allowed parameter the first one, it's possible to infer where the array parameter begins, even after Perl flattens the arguments in the subroutine call.

Top comments (0)