Perl Weekly Challenge: I see strings reversed… sum numbers, too…

Perl Weekly Challenge 379‘s tasks are “Reverse String” and “Armstrong Number”.

So, I think for music this week, I’ll give you a number by Armstrong.1

Task 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"

Approach

Ah! Since I can’t use the builtin functions, there’s a couple ways we can do this: split the string into characters, manipulate the array, and then join it back into a string, or pull the characters off the string one at a time and append them to a new string.

Raku

For Raku, I did a mix: split the string into characters, but then build a reversed string as I process the characters.

sub my_reverse($str) {
  my $rts = "";
  for $str.comb -> $char {
    $rts = $char ~ $rts;
  }
  $rts;
}

View the entire Raku script for this task on GitHub.

$ raku/ch-1.raku
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"

Perl

And the same in Perl.

sub my_reverse($str) {
  my $rts = "";
  foreach my $char (split //, $str) {
    $rts = $char . $rts;
  }
  $rts;
}

View the entire Perl script for this task on GitHub.

Python

Python, makes it really easy to loop over a string character by character.

def my_reverse(string):
  gnirts = ""
  for char in string:
    gnirts = char + gnirts
  return gnirts

View the entire Python script for this task on GitHub.

Elixir

And the same thing in Elixir

def my_reverse(str) do
  String.codepoints(str)
  |> Enum.reduce("", fn char, rts -> char <> rts end)
end

View the entire Elixir script for this task on GitHub.


Task 2: Armstrong Number

You are given two integers, $base and $limit.

Write a script to find all Armstrong numbers in base $base that 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)

Approach

This took me a moment to grok. It’s obvious that for a single digit number n, n1 will be n. So I looked at 153: 1103 + 5103 + 3103 = 15310. Ahh! But I have to worry about the numbers being in different bases, although all the output appears to be in base10.

So, to get the digits in whatever base we’re in, we do modulo division of the number by the base to get the least significant digit, and then we do an integer division (DIV) of the number by the base to discard that least significant digit and shift everything right, and we repeat until the number is 0. As a special case, if the number is 0 to begin with, the number has one digit: 0.

Edit: Oriel Jutty pointed out that I misread the requirement that the solution only produce output strictly less than the limit. My bad!

Raku

In Raku, the operators we need are %, div, and **.

sub digits($base, $num is copy) {
  return [0] if $num == 0; # base case
  my @digits;
  while ($num  > 0) {
    @digits.unshift($num % $base);
    $num = $num div $base;
  }
  @digits;
}

sub armstrong($base, $limit) {
  my $n = 0;
  my @armstrong;
  while ($n <= $limit) {
    my @dig = digits($base, $n);
    my $pow = @dig.elems;
    my $n2  = @dig.map({ $_ ** $pow }).sum;
    @armstrong.push($n) if $n == $n2;
    $n++;
  }
  @armstrong
}

View the entire Raku script for this task on GitHub.

$ raku/ch-2.raku
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)

Perl

Perl has % and **, but it doesn’t have a div, so we have to use a combination of / and int.

use List::AllUtils qw( sum );
sub digits($base, $num) {
  return (0) if $num == 0; # base case
  my @digits;
  while ($num  > 0) {
    unshift @digits, ($num % $base);
    $num = int($num / $base);
  }
  @digits;
}

sub armstrong($base, $limit) {
  my $n = 0;
  my @armstrong;
  while ($n <= $limit) {
    my @dig = digits($base, $n);
    my $pow = @dig;
    my $n2  = sum map { $_ ** $pow } @dig;
    push @armstrong, $n if $n == $n2;
    $n++;
  }
  @armstrong
}

View the entire Perl script for this task on GitHub.

Python

In Python, the operators we need are %, // (floor division), and **. Because it’s difficult to push new digits on the beginning of a list, I’m appending them to the end and then just reversing the list.

def digits(base, num):
  if num == 0: return [0]
  digits = []
  while num > 0:
    digits.append( num % base )
    num = num // base
  return digits[::-1] # reverse them

def armstrong(base, limit):
  n = 0
  armstrong = []
  while n <= limit:
    dig = digits(base, n)
    pow = len(dig)
    n2  = sum([ d ** pow for d in dig ])
    if n == n2: armstrong.append(n)
    n += 1
  return armstrong

View the entire Python script for this task on GitHub.

Elixir

But Elixir lets me cheat using the Integer.digits/2 function, which returns the ordered digits for the given integer in base base. So all I have to do is check to see if it’s an Armstrong number or not.

def armstrong(_, limit, n, armstrong) when n > limit,
  do: armstrong

def armstrong(base, limit, n, armstrong) do
  dig = Integer.digits(n, base)
  pow = length(dig)
  n2  = Enum.reduce(dig, 0, fn d, n2 ->
    n2 + (d ** pow)
  end)
  armstrong = if n == n2 do
    armstrong ++ [n]
  else
    armstrong
  end
  armstrong(base, limit, n+1, armstrong)
end

def armstrong(base, limit) do
  armstrong(base, limit, 0, [])
end

View the entire Elixir script for this task on GitHub.


Here’s all my solutions in GitHub: https://github.com/packy/perlweeklychallenge-club/tree/challenge-379-packy-anderson/challenge-379/packy-anderson

  1. The song was written by Bob Thiele and George David Weiss, but it was first recorded by Louis Armstrong. ↩︎

Leave a Reply