Perl Weekly Challenge: Week 379
Challenge 1:
Reverse String
You are given a string.
Write a script to reverse the given string without using standard reverse function.
Example 1
Input: $str = ""
Output: ""
Example 2
Input: $str = "reverse the given string"
Output: "gnirts nevig eht esrever"
Example 3
Input: $str = "Perl is Awesome"
Output: "emosewA si lreP"
Example 4
Input: $str = "v1.0.0-Beta!"
Output: "!ateB-0.0.1v"
Example 5
Input: $str = "racecar"
Output: "racecar"
If we can't use reverse() (or .flip() in Raku) it's not a big deal. We can
achiece the same result splitting the string into individual characters and
placing them into a LIFO (Last In First Out) Stack data structure. When we
concatenate the contents of the stack, we will have the reversed
version of the original string. While neither Perl nor Raku have a native Stack
data type, we can easily emulate it in both languages with lists. In fact our
solution will be a one-liner in both languages.
Here's the Raku version. It's been split up into multiple lines for clarity.
First we define our "stack".
my @a;
Next we split up the first command-line argument into individual characters with
.comb() and for each character...
for @*ARGS[0].comb -> $c {
...we add it to the beginning of the list with .unshift(). This is equivalent
to a "push" operation on a stack. (Both Raku and Perl have push() and pop()
functions but they work on the end of a list.)
@a.unshift($c)
};
Finally we .join() up the stack and print it out with .say().
@a.join.say
This is the Perl version. It is almost directly equivalent to the Raku version.
my @a;
for my $c (split//, shift) {
unshift @a, $c
}
say join q{}, @a
Challenge 2:
Armstrong Number
You are given two integers,
$baseand$limit.Write a script to find all Armstrong numbers in base
$basethat are less than $limit.If raising each of the digits of a nonnegative integer to the power of the total number of digits, then taking the sum, equals the original number, it is an Armstrong number.
Example 1
Input: $base = 10, $limit = 1000
Output: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407)
Example 2
Input: $base = 7, $limit = 1000
Output: (0, 1, 2, 3, 4, 5, 6, 10, 25, 32, 45, 133, 134, 152, 250)
Example 3
Input: $base = 16, $limit = 1000
Output: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 342, 371, 520, 584, 645)
I produced a solution that got the correct answer for example 1 vety quickly but it
did not work properly for the other examples where $base does not equal 10.
First the preliminaries:
We create a list to hold the results.
my @armstrongs;
Then we iterate through the integers from 0 to, upto but not including, $limit.
for 0 ..^ $limit -> $n {
Now the tricky bit. We have to convert $n which is a base-10 number to
base-$base. This is easy to do with .base().
my $N = $n.base($base);
And we find the length of that number with .chars.
my $power = $N.chars;
Then as the spec directs, using .comb() and .map(), we take each digit of $N
and raise it to the power of the total number of digits (i.e. $power), .sum()
all these values and compare them to N to see if it is an Armstrong number.
It didn't work.
After some experimentation, I figured out what I had missed. Each digit of
$N has to be converted back to base-10 (for which Raku has the handy .parse-base())
and compare the sum of those numbers to "the original number" which is $n not $N.
If they are equal, we can add $n to our list of Armstrong numbers.
if $N.comb.map({ $_.parse-base($base) ** $power }).sum == $n {
@armstrongs.push($n);
}
}
After all that exitement, all that remains is to .join() up the Armstrong numbers
with commas and spaces and print them out with .say().
@armstrongs.join(q{, }).say;
For Perl, we need replacements for .base(), .parse-base() and .sum()
sum() has been used in many previous challenges so was quickly cut and pasted
into the script. I thought I might have to write code for the other two. It's
a good thing I checked first because way back in PWC 2, there was a challenge to convert to and from
base 35. I was able to generalize and modernize that.
fromBase() takes two parameters, a $number in a certain $base and returns
the value of that number in base-10.
sub fromBase($number, $base) {
I think I should have called this $magnitude but I'm fuzzy on math terminology.
In any case, $scale reprresents the number of digits execept units.
my $scale = (length $number) - 1;
We need to map digits in this base to their base-10 values. For base 0 to 9 there is
a one to one coresspondance but for higher bases which use letters to represent digits,
the base-10 value cannot be represented by a single character. so we have a Hash
that maps digts to values. The mapping is done by the second line. For base-7,
the keys would be 0-6 and the values 0-6 but for Hexadecimal (base-16,) the keys
would be 0-F and the values 0-15.
The way this is written, we can only deal with bases 1-36 (and 1 doesn't really count.) In professional code I would have done some parameter checking but I haven't here.
my %digits;
@digits{0..9, 'A'..'Z'} = 0 .. $base - 1;
Here is where we will store the result of the conversion.
my $result;
Now we split $numberinto individual digits...
for my $digit (split //, $number) {
...and look up its' value in %digits. I msde a small effort to do some error
checking but it really should be more rigorous in professional code.
my $base10 = $digits{uc $digit} // die "malformed base-$base number\n";
This value is multiplied by the $base, raised to the power of $scale and added
to $result.
$result += $base10 * $base ** $scale;
As we move to the next decimal place to the right, $scale is decremented.
$scale--;
}
Finally, the converted number in$result is returned.
return $result;
}
toBase() also takes two parameters, a $number in base-10 and $base to convert
it into. It returns tha value of that number in base-$base.
sub toBase($number, $base) {
Again we need the digits used in in $base but this time, the values assoxiated with each
digit increase sequentially so we can make @digits a List and just use the index of
each element as the value and the element itself as the key.
my @digits = (0 .. 9, 'A' .. 'Z')[0 .. $base - 1];
The result is also stored as a list.
my @result;
If $number is 0, we can skip all the other processing.
if ($number == 0) {
return 0;
}
If not, while it is greater 0...
while ($number > 0) {
Using the modulo operator % we find the remainder when dividin $number by $base.
This will give the value of the lowest place digit.
my $digit = int($number % $base);
The digit is used as a key into @digits and the value is added to the beginning of
the @result.
unshift @result, $digits[$digit];
The digit is removed from $number and the loop continues.
$number = int($number / $base);
}
Finally, we .join() the @result back together into a single string and return it.
return join '', @result;
}
Now the rest of the script is just like Raku.
my @armstrongs;
for my $n (0 .. $limit - 1) {
my $N = toBase($n, $base);
my $power = length $N;
if (sum(map { fromBase($_, $base) ** $power } split //, $N) == $n) {
push @armstrongs, $n;
}
}
say join q{, }, @armstrongs;