Perl Weekly Challenge: Week 106

Challenge 1:

Maximum Gap

You are given an array of integers @N.

Write a script to display the maximum difference between two successive elements once the array is sorted.

If the array contains only 1 element then display 0.

Example
Input: @N = (2, 9, 3, 5)
Output: 4

Input: @N = (1, 3, 8, 2, 0)
Output: 5

Input: @N = (5)
Output: 0

Here's how I solved it in Perl:

if (scalar @ARGV == 1) {
    say 0;
} else {
    my @N = sort @ARGV;

    my $previous = $N[0];
    my $largest = -Inf;
    for my $i (1 .. scalar @N - 1) {
        my $gap =  $N[$i] - $previous;
        if ($gap > $largest) {
            $largest = $gap;
        }
        $previous = $N[$i];
    }
    say $largest;
}

(Full code on Github.)

A special case is if there is only one element in the array @N, we know there is no gap so 0 is printed and we exit.

Otherwise, once @N is sorted, it is simply a matter of taking each element (starting from the second) and comparing it to the previous element. If the difference between them is larger than the previous largest distance, that value becomes the new largest difference. At the end, the largest difference is printed.

This is the Raku version:

    if @N.elems == 1 {
        say 0;
    } else {
        @N = @N.sort;
        my $previous = @N[0];
        my $largest = -∞;
        for 1 ..^ @N.elems -> $i {
            my $gap =  @N[$i] - $previous;
            if $gap > $largest {
                $largest = $gap;
            }
            $previous = @N[$i];
        }
        say $largest;
    }

(Full code on Github.)

Challenge 2:

Decimal String

You are given numerator and denominator i.e. $N and $D.

Write a script to convert the fraction into decimal string. If the fractional part is recurring then put it in parenthesis.

Example
Input: $N = 1, $D = 3
Output: "0.(3)"

Input: $N = 1, $D = 2
Output: "0.5"

Input: $N = 5, $D = 66
Output: "0.0(75)"

Solving this in Raku is super easy because it has a builtin method for exactly this task.

my ($non-rep, $repeating) = ($N / $D).base-repeating;
if $repeating ne q{} {
    $repeating = "($repeating)";
}
say "$non-rep$repeating";

(Full code on Github.)

The .base-repeating() method returns a two-element array. the first element will be the non-repeating part of the fraction. If the fraction has a recurring part, it will be stored in the second element. Because the spec says the recurring part has to be enclosed in parentheses we need to check if that second element is non-empty otherwise we can just print the returned array.

For Perl we have no such luck and will have to implement an algorithm for this problems ourselves. I must confess I had no idea how to do that so I did a bit of googling and found this page. My code is based on the C++ solution presented there.

my $output = '';
if ($N == 0) {
    $output = '0';

If the numerator is 0, the answer will be 0 so we can just print that and be done.

} else {
    $output .= (($N < 0) ^ ($D < 0)) ? q{-} : q{};

Otherwise we determine the sign of the result and add an indicatory - to the output if needed.

    my $numerator = abs($N);
    my $denominator = abs($D);

To avoid sign issues in the rest of the algorithm, we take the absolute values of the numerator and denominator.

    $output .= ($numerator / $denominator);

The whole part of the result will be represented by the integer division of the numerator by the denominator. By default, division in Perl is floating-point (which is usually what you want) but you can switch to integer division with the use integer pragma.

    if ($numerator % $denominator != 0) {

If the numerator and denominator divide evenly we are finished otherwise there is a fractional component.

        $output .= '.';

So we add a decimal point to the output.

        my $remainder = $numerator % $denominator;
        my %remainderMap;

        my $index = 0;
        my $repeating = undef;

We set up some variables to represent the modulo remainder of the fraction left to be processed (this is stored in a hash.) and if the fraction is repeating. (If so, where it begins repeating.)

Now while we still have a fraction to process and it hasn't begun repeating...

        while ($remainder > 0 && !$repeating) {
            if (exists $remainderMap{$remainder}) {

                $index = $remainderMap{$remainder};
                $repeating = 1;
                last;

If we have seen this remainder value already, it means the fraction is repeating so we can store the location where this happened and exit the loop.

            } else {
                $remainderMap{$remainder} = (length $output) - 1;
            }

Otherwise we add the remainder to our hash of seen values.

            $remainder *= 10;

And increase its' value by an order of 10.

            my $temp = $remainder / $denominator;
            $output .= $temp;

This is used to determine what to add to the output.

            $remainder = $remainder % $denominator;

Finally, we remove the seen part of the remainder and continue the loop with its new value.

        }

        if ($repeating) {
            $output .= ')';
            my $x = substr($output, $index, 1);
            substr($output, $index, 1) = "$x(";
        }

If after we have finished processing the fraction it is determined to be recurring, the recurring part is added to the output enclosed in parentheses as the spec requires.

    }
}

say $output;

(Full code on Github.)

Finally the output string is printed.