Perl Weekly Challenge: Week 254

Challenge 1:

Three Power

You are given a positive integer, $n.

Write a script to return true if the given integer is a power of three otherwise return false.

Example 1
Input: $n = 27
Output: true

27 = 3 ^ 3
Example 2
Input: $n = 0
Output: true

0 = 0 ^ 3
Example 3
Input: $n = 6
Output: false

The condition in the spec will be satisfied if the cube root of $n is an integer. Which seems very easy to do.

say @*ARGS[0] ** (1/3) %% 1

Raku has a .sqrt() method for square roots but not one specifically for cube roots. But raising a number to the power of one third is the same thing and we can do that with the exponention or ** operator. %% is the integer modulus operator. %% 1 will be true if the cube root is exactly divisible by one or in other words, an integer.

This works for the examples but there is a catch. If we try $n = 64 for example, we should get True because 64 is 4 to the power of 3 but we actually get False. Why? It is due to the way Raku (and it should be noted many other languages) represent numbers internally. 64 ** (1/3) actually comes out as 3.9999999999999996—not an integer. A way around this to not have our calculations be so precise. With .round(), the cube root is rounded off to the nearest thousandth.

say (@*ARGS[0] ** (1/3)).round(0.001) %% 1

(Full code on Github.)

This will still fail for really big values of $n I think but it works for a much larger range of values than before.

Perl has the same problem and we can use the same answer but there are added complications. One, we do not have .round() so we have to use a module. Math::Round has a function—and this trips me up every time—not round() but nearest() which does what we want. Two, there is no simple equivalent to %% 1. Another way to determine if a value is an integer is to compare it to the output of sprintf("%d"). If it is equal, the number is an integer.

$r = nearest(0.001, shift() **(1/3)); say $r == sprintf("%d", $r) ? "true" : "false"

(Full code on Github.)

Challenge 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.

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"

My initial idea for a solution involved a grand scheme of creating hashes of string positions and reordering the keys etc. but I thought about it and actually it is a lot more simple.

First we split the input string into a list of individual characters.

my @chars = $s.comb;

We also create another list which will be used as a LIFO (Last In First Out) stack.

my @vowels;

We traverse the list of characterss and every time we find one which is a vowel, we add it to @vowels.

for @chars.keys -> $c {
    if @chars[$c] ∈ <a A e E i I o O u U> {
        @vowels.push(@chars[$c]);
    }
}

Then we traverse @chars again. This time, when we see a vowel, we replace it with one .pop()ed off the end of @vowels.

for @chars.keys -> $c {
    if @chars[$c] ∈ <a A e E i I o O u U> {
        @chars[$c] = @vowels.pop;
    }
}

Finally we join the characters back together into a string and print it out.

@chars.join.say;

This mostly works except for i.e. example 4. The output is auiU but what we want is Auiu.

Luckily allowing for capitalization is an easy fix to our code. All we need to do is split up the second loop so...

for @chars.keys -> $c {

...if we come across an upper-case vowel...

    if @chars[$c] ∈ <A E I O U> {

...its' replacement is made upper-case with .uc() regardless of what it may have been stored as intitially.

        @chars[$c] = @vowels.pop.uc;
    }

Or if we come across a lower-case vowel...

    elsif @chars[$c] ∈ <a e i o u> {

...its' replacement is made lower-case with .lc().

        @chars[$c] = @vowels.pop.lc;
    }
}

(Full code on Github.)

This is the Perl version, a straightforward translation from Raku except because we don't have a member-of operator like in Raku, we use a regular expression match instead.

my @chars = split //, $s;
my @vowels;

for my $c (keys @chars) {
    if ($chars[$c] =~ /[aAeEiIoOuU]/) {
        push @vowels, $chars[$c];
    }
}

for my $c (keys @chars) {
    if ($chars[$c] =~ /[AEIOU]/) {
        $chars[$c] = uc pop @vowels;
    }
    elsif ($chars[$c] =~ /[aeiou]/) {
        $chars[$c] = lc pop @vowels;
    }
}

say join q{}, @chars;

(Full code on Github.)