DEV Community

Bob Lied
Bob Lied

Posted on

PWC 254, Task 2: I, Too, Wish to Make the "Vowel Movement" Joke

Task 2: Reverse Vowels

You are given a string, $s. Write a script
to reverse all the vowels (a, e, i, o, u)
in the given string.
Enter fullscreen mode Exit fullscreen mode
  • Example 1: Input: s = "Raku" Output: "Ruka"
  • Example 2: Input: $s = "Perl" Output: "Perl"
  • Example 3: Input: $s = "Julia" Output: "Jaliu"
  • Example 4: Input: $s = "Uiua" Output: "Auiu"

Ruminations

When the subject is vowels, I'm tempted to make the theme music "Old McDonald", but that is too inane even for me. Let's go with Red Hot Chili Peppers, Snow. The more I see, the less I know. Hey, oh.

Example 4 gives us an interesting implied requirement: the case of the result should match the case of the input.

My first thought is to do something like record the indexes of the vowels, and then do some math to map the indexes backward and indirectly access the letters of the string for swaps. My brain is already off-by-one just thinking about it.

Reversing a list is the classic computer science last-in-first-out stack. If we can get a list of the vowels in order, we can generate them in reverse order by popping off a stack. Getting that list is easily done with a regular expression match; in array context, it will directly give us the list:

sub revVow($s) {
    state $isVowel = qr/[aeiou]/i;

    my @v = $s =~ m/$isVowel/g;
Enter fullscreen mode Exit fullscreen mode

I'm saving the pattern for a vowel in a variable because I'm going to need it again. I'm using state because that makes it a static variable that only needs to get evaluated once. I need /g (global) at the end of the expression to get the list of all matches, and not just a count.

Leaving consonants untouched and moving the vowels is going to require something that iterates over the letters. We'll copy consonants from input to output, but each time we see a vowel, we'll pop the top element from the @v stack.

    my $rev;
    for ( split(//, $s) )
    {
        my $next = ( /$isVowel/ ? pop @v : $_ );
        $rev .= ( /\p{Uppercase}/ ? uc $next : lc $next );
    }
    return $rev;
Enter fullscreen mode Exit fullscreen mode

A few Perl points in this code:

  • Instead of making up a variable name for each character, I'm using the implicit topicalizer, $_. I generally prefer naming things, but in two lines of code without any nested blocks that have their own $_, it's easy enough to understand.
  • The implicit $_ is used in the pattern matching statements.
  • The regular expression $isVowel can be interpolated for pattern matching. It's static in this case, so I'm just using the variable to avoid repeating myself, but imagine that the definition could change (for different languages, say).
  • Perl has amazing Unicode support, including an extensive list of character classes and properties. I'm exploiting that to check for uppercase characters in /\p{Uppercase}/ in a very readable way.

Weekly Challenge problems generally keep things simple by restricting the strings to good old ASCII. I thought briefly about what it would take to extend the problem to a more general definition of vowels. I know nothing about non-European languages, but maybe we could handle accents and umlauts? In my brief search, I didn't find an easy way to specify a pattern match for something like "all forms of A with any kind of diacritical marks."

Looking over early answers, there was a clever solution that looked like this:

$str =~ s/[aeiou]/pop @v/ge;
Enter fullscreen mode Exit fullscreen mode

It's a straight substitution of vowels, except that the replacement comes from executing a bit of Perl code. That feature is enabled by adding /e to the substitution operator. I usually judge that as too magic, but in this case I think it's simple enough to be kind of elegant.

Top comments (0)