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
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"
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;
}
}
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;