Dividing Powers with Raku

by Arne Sommer

Dividing Powers with Raku

[79] Published 27. June 2020.

This is my response to the Perl Weekly Challenge #066.

Challenge #066.1: Divide Integers

You are given two integers $M and $N.

Write a script to divide the given two integers i.e. $M / $N without using multiplication, division and mod operator and return the floor of the result of the division.

Example 1
Input: $M = 5, $N = 2
Output: 2
Example 2
Input: $M = -5, $N = 2
Output: -3
Example 3
Input: $M = -5, $N = -2
Output: 2

Division is just subtraction in a loop. Off we go. Off we go. Off we go...:

File: divint
#! /usr/bin/env raku

unit sub MAIN (Int $M, Int $N where $N != 0, :$verbose);  # [1]

my $count  = 0;                                           # [2]
my $negate = $M.sign != $N.sign;                          # [3]
my $M-abs  = $M.abs;                                      # [4]
my $N-abs  = $N.abs;                                      # [4]

while $M-abs >= $N-abs                                    # [5]
{
  $M-abs -= $N-abs;                                       # [6]
  $count++;                                               # [7]

  say "M: $M-abs N: $N-abs = $count (negate $negate)" if $verbose;
}

if $negate                                                # [8]
{
  $count = -$count;                                       # [9]
  $count-- if $M-abs;                                     # [10]
}

say $count;                                               # [2]
We have to take care of two things; the sign and any remainder (but only if the sign is negative).

[1] Divison by zero is a bad thing, so we prevent that possibility for $N.

[2] The result.

[3] The result is a negative integer if one and only one of $M and $M is neagtive, else it is positive. We use sign to get the sign (-1, 0 or 1).

[4] Are we counting down to - or up to - zero? That depends on the sign. But we can count down if we ensure that both values are positive. That is done with abs, that removes any negative sign. The sign of the result has been taken care of in [3].

[5] As long as the remainder is larger or equal to the divisor,

[6] • subtract the divisor.

[7] • increase the count, which is the end result.

[8] Should the answer be negative?

[9] • negate it.

[10] • subtract 1 (the «floor» part of the challenge description) if we have a remainder.

See docs.raku.org/routine/sign for more information about the sign method.

See docs.raku.org/routine/abs for more information about the abs method.

Running it:

$ raku divint 5 2
2

$ raku divint -5 2
-3

$ raku divint -5 -2
2

$ raku divint 5 -2
-3

With verbose mode (which would benefit from a rethink, me think...):

$ raku divint --verbose 5 2
: Negate the result: No
: M: 3 N: 2 = 1
: M: 1 N: 2 = 2
2

$ raku divint --verbose 25 2
: Negate the result: No
:M: 23 N: 2 = 1
:M: 21 N: 2 = 2
:M: 19 N: 2 = 3
:M: 17 N: 2 = 4
:M: 15 N: 2 = 5
:M: 13 N: 2 = 6
:M: 11 N: 2 = 7
:M: 9 N: 2 = 8
:M: 7 N: 2 = 9
:M: 5 N: 2 = 10
:M: 3 N: 2 = 11
:M: 1 N: 2 = 12
12

A Perl Version

Just for fun...

Perl does not have a «sign» function, so I wrote one. I chose to do command line handling manually. Other than that, the code is pretty much the same as the raku version. Except longer...

File: divint-perl
#! /usr/bin/env perl

use feature 'say';
use feature 'signatures';
no warnings qw(experimental::signatures);

my $verbose = 0;

if (@ARGV && $ARGV[0] eq "--verbose")
{
  $verbose++;
  shift @ARGV;
}

my $M = shift(@ARGV) || die 'Specify $M and $N';
my $N = shift(@ARGV) || die 'Specify $N';

die '$M is not an integer' unless int($M) == $M;
die '$N is not an integer' unless int($N) == $N;

die "Unable to divide by zero" if $N == 0;

sub sign ($value)
{
  return  1 if $value  > 0;
  return  0 if $value == 0;
  return -1 if $value < 0;
}

my $count  = 0;
my $negate = sign($M) != sign($N);
my $M_abs  = abs($M);
my $N_abs  = abs($N);

say ": Negate the result: ", $negate ? 'Yes' : 'No' if $verbose;
			
while ($M_abs >= $N_abs)
{
  $M_abs -= $N_abs;
  $count++;

  say "M: $M_abs N: $N_abs = $count" if $verbose;
}

if ($negate)
{
  $count = -$count;
  $count-- if $M_abs;
}

say $count;

Challenge #066.2: Power Integers

You are given an integer $N. Write a script to check if the given number can be expressed as mn where m and n are positive integers. Otherwise print 0.

Please make sure m > 1 and n > 1.

BONUS: If there are more than one way to express the given number then print all possible solutions.

Example 1
For given $N = 9, it should print 32 or 3^2.

Example 2
For given $N = 45, it should print 0.

Example 3
For given $N = 64, it should print all or one of 8^2 or 2^6 or 4^3.

File: powint
#! /usr/bin/env raku

multi sub MAIN (Int $N where $N.is-prime, :$one)     # [1] [2]
{
  say 0;
}

multi sub MAIN (Int $N where $N > 1, :$one)          # [3] [2]
{
  my $match = 0;                                     # [4]
  for 2 .. $N.sqrt -> $base                          # [5]
  {
    for 2 .. Inf -> $exp                             # [6]
    {
      my $candidate = $base ** $exp;                 # [7]
      last if $candidate > $N;                       # [8]

      (say "$base^$exp"; $match++; exit if $one) if $candidate == $N;  # [9]
    }
  }
  say 0 unless $match;                               # [10]
}

[1] Prime numbers cannot be factoralized, so we can check for that (with is-prime) up front. This speeds up the program in case of prime numbers, but isn' strictly necessary

[2] Use the «--one» command line option if you only want one result.

[3] The where clause ensures that the value is as requested by the challenge.

[4] Do we have a match (or more), so that we can give the «0» error message in [10].

[5] Iterate over the base number, from 2 to the square root of the original number. This will stop our loop when we have reached the largest number (for $base) that we can fit (in $N) by squaring it.

[6] The exponent, as an infinite loop.

[7] A possible answer?

[8] If the possible answer is higher than the initial value, we can abort the inner (infinite) loop.

[9] We have a match, print it, count it, and exit if we only wanted one result. Note the funny use of parens (the grouping operator) to stack more than one expressions into a postfix if. Brackets are also allowed, instead of the parens.

[10] No match?, say so.

See docs.raku.org/routine/is-prime for more information about is-prime.

See docs.raku.org/routine/** for more information about the exponentiation operator **.

Running it:

$ raku powint 9
3^2

$ raku powint 45
0

$ raku powint 64
2^6
4^3
8^2

$ raku powint --one 64
2^6

A Perl Version

of this one as well...

File: powint-perl
#! /usr/bin/env perl

use feature 'say';

my $one = 0;

if (@ARGV && $ARGV[0] eq "--one")
{
  $one++;
  shift @ARGV;
}

my $N = shift(@ARGV) || die 'Specify $N';

die '$N is not an integer' unless int($N) == $N;
die '$N must be an integer larger than 1' unless $N > 1;

my $match = 0;

for my $base (2 .. sqrt($N))
{
  my $exp = 1;
  while ($exp++)
  {
    my $candidate = $base ** $exp;
    last if $candidate > $N;

     if ($candidate == $N)
     {
       say "$base^$exp";
       $match++;
       exit if $one;
     }
  }
}

say 0 unless $match;

And that's it.