DEV Community

Bob Lied
Bob Lied

Posted on • Edited on

PWC 240 Just a slice. No loop, please. I'll eat it here.

Perl Weekly Challenge 240 gives us exercises in array operations. A quick read suggests that we'll be looping over array elements, but Perl lets us treat arrays in bulk.

Task 1: Acronym

You are given an array of strings and a
check string. Write a script to find out
if the check string is the acronym of the
words in the given array.
Enter fullscreen mode Exit fullscreen mode

Example 1

Input: @str = ("Perl", "Python", "Pascal")
$chk = "ppp"
Output: true

Example 2

Input: @str = ("Perl", "Raku")
$chk = "rp"
Output: false

Example 3

Input: @str = ("Oracle", "Awk", "C")
$chk = "oac"
Output: true

Exposition

Creating an acronym can be looked at as doing a transformation on each element of a list: take the first character and convert it to lower-case. A solution for every language since Algol would involve using a loop to index the elements of the array, but we can do better in Perl. This is a perfect application for map.

sub acronym($chk, @str)
{
    return $chk eq join("", map { lc substr($_, 0, 1) } @str)
}
Enter fullscreen mode Exit fullscreen mode

This reads backwards and inside-out, so in case it's not obvious, let's disassemble it.

  • map {...} @str -- we're going to apply a transformation to every element of the @str array
  • { lc substr($_, 0, 1) } -- the transformation we're applying is to take the first character of the array element ($_) with substr and then take the lower-case version of that character. Perl nicely handles UTF8 characters here, so this will work with things like accented characters and Greek letters.
  • join("", map...) -- the result of the map will be a list of single-character strings. Join-ing them with an empty string will produce the acronym.
  • return $chk eq ... -- the only thing remaining then is to do the comparison and return the boolean value

Digression

Using lc reminds me of Perl's built-in letter-casing functions. The complement of lc (lower-case) is uc (upper-case). There's also the lesser known ucfirst and lcfirst, which operate on the first letter of a string.

Even more obscure is that upper-casing and lower-casing can be specified in string interpolation of quoted strings. Suppose we have a string variable, and we want the upper-case version of it inside a message string. The most obvious (to me) solution is to construct a string using the uc function:

my $priority = "critical";
my $message = "Assign all ". uc(critical) . " bugs to Bob";
Enter fullscreen mode Exit fullscreen mode

But Perl can do case changes while evaluating the quotes:

my $message = "Assign all \U$priority\E bugs to Bob";
Enter fullscreen mode Exit fullscreen mode

The case conversion functions are documented in perldoc perlfunc. The case conversions in string interpolation are documented in perldoc perlop under "Quote and quote-like operators".

Extra-double-secret bonus super-nerd tip: string substitution in vim also supports the Perl syntax of \U and \L for case conversion.

Task 2: Build Array

You are given an array of integers.
Write a script to create an array
such that new[i] = old[old[i]]
where 0 <= i < new.length.
Enter fullscreen mode Exit fullscreen mode

Example 1

Input: @int = (0, 2, 1, 5, 3, 4)
Output: (0, 1, 2, 4, 5, 3)

Example 2

Input: @int = (5, 0, 1, 2, 3, 4)
Output: (4, 5, 0, 1, 2, 3)

Exposition

We could take this problem statement literally and make a loop over the indices of @int. But this problem has a simplifying quirk: the action of every loop iteration will be nothing more than an indirect index of the array into itself.

my @new;
for ( my $i = 0 ; $i <= $#int ; $i++ )
(
    $new[$i] = $int[$int[$i]]
)
Enter fullscreen mode Exit fullscreen mode

We can take all those indexes in one group using an array slice. Instead of using a single integer as an index, and getting back a single scalar value, we can use a list of index values and get back a list of corresponding values. The task becomes an almost trivial one-liner (trivial, that is, if array slices are obvious to you).

sub buildArray(@int)
{
    return [ @int[@int] ];
}
Enter fullscreen mode Exit fullscreen mode

As usual, I prefer to use array references when returning an array from subroutine calls, because (1) that saves the overhead cost of copying lists, and (2) array references are easier to test using unit test frameworks.

Top comments (2)

Collapse
 
simongreennet profile image
Simon Green • Edited

I'm not sure what version of Perl you are using, but for me (and according to Perldocs), ucfirst only applies to the first character of the string

perl -E "say ucfirst 'what\'s up?'"
What's up?
Enter fullscreen mode Exit fullscreen mode
Collapse
 
boblied profile image
Bob Lied

You're right. I conflated some things I was trying with Perl and title case in Vim and got sideways.