Perl Weekly Challenge: Week 98

Advent of Code update: While I was starting to feel the heat a little bit things were moving forward nicely until the week before Christmas when disaster struck in real life. I did manage to get a few more problems done but my mind was elsewhere and I reluctantly had to let it (and the weekly challenge go.) Even after things got back to normal, I was buried with work to catch up on so I am only now getting back into action. Of the 49 AOC problems, I completed 43. Of those, about 35 I did on the same day. The other 8 I had to get hints from other participants on Reddit (only on algorithms though. I didn't cheat and copy anyones code.) There are 6 problems I haven't even tried yet. I am determined to do them though.

I have also missed 6 weeks of the weekly challenge. I am going to catch up with those too, hopefully before the 100th anniversary in two weeks time.

On to this weeks challenges!

Challenge 1:

Read N-characters

You are given file $FILE.

Create subroutine readN($FILE, $number) returns the first n-characters and moves the pointer to the (n+1)th character.

Example:
Input: Suppose the file (input.txt) contains "1234567890"
Output:
    print readN("input.txt", 4); # returns "1234"
    print readN("input.txt", 4); # returns "5678"
    print readN("input.txt", 4); # returns "90"

I had to think about this a little bit because most input/output I do in Perl scripts is on a line by line basis. Another issue is the spec stipulates that the file position pointer shouldn't be lost between calls to the subroutine. I could have kept the filehandle as a global variable but the spec also says the subroutine should take a filename not a filehandle. If I were to open and close the filehandle in the subroutine, I would have lost the position. I solved the problem with a state variable in readN() This kind of variable works similiarly to a regular my variable but maintains its value between invocations. So initially I set it to undef. If when I call readN() it is not defined I know this is the first time the subroutine has been called so I use it to hold a filehandle that I open. If when I call readN() it is defined, I know that the filehandle exists so there is no need to open it again. The code looks like this:

sub readN {
    my ($filename, $number) = @_;
    my $buffer;
    state $fh = undef;

    if (!defined $fh) {
        open $fh, '<', $filename or die "$OS_ERROR\n";
    }

    read $fh, $buffer, $number or die "$OS_ERROR\n";

    return $buffer;
}

(Full code on Github.)

The translation to Raku was fairly straightforward though I had to learn a lot of new syntax. Luckily the Raku docs are pretty good. Note the use of the try block to simplify error handling.

sub readN(Str $filename, Int $number) {
    state IO::Handle $fn = Nil;

    try {
        unless $fn {
            $fn = $filename.IO.open(:r);
        }

        return $fn.readchars($number);
    }

    die $!;
}

(Full code on Github.)

Challenge 2:

Search Insert Position

You are given a sorted array of distinct integers @N and a target $N.

Write a script to return the index of the given target if found otherwise place the target in the sorted array and return the index.

Example 1
Input: @N = (1, 2, 3, 4) and $N = 3
Output: 2 since the target 3 is in the array at the index 2.
Example 2
Input: @N = (1, 3, 5, 7) and $N = 6
Output: 3 since the target 6 is missing and should be placed at the index 3.
Example 3
Input: @N = (12, 14, 16, 18) and $N = 10
Output: 0 since the target 10 is missing and should be placed at the index 0.
Example 4
Input: @N = (11, 13, 15, 17) and $N = 19
Output: 4 since the target 19 is missing and should be placed at the index 4.

This was easy to solve. You simply have to traverse @N and as it is already in sorted order, when you come across an element that is greater or equal to $N (not the best choice of variable names) you can insert before it. The only issue is what if no element is greater or equal? The answer is to make the default position the end of the array. Here is the Raku version:

sub MAIN(
    *@N #= a series of atleast 2 distinct integers. The last element will be
        #= used as a target to search in the previous elements.
    where {@_.elems > 1 }
) {
    my $N = @N.pop;
    my $pos = @N.elems;

    for 0 ..^ @N.elems -> $i {
        if @N[$i] >= $N {
            $pos = $i;
            last;
        }
    }

    say $pos;
}

(Full code on Github.)

And this is the relevant bits from the Perl version:

my $N = pop @ARGV;
my $pos = scalar @ARGV;

for my $i (0 .. scalar @ARGV - 1) {
    if ($ARGV[$i] >= $N) {
        $pos = $i;
        last;
    }
}

say $pos;

(Full code on Github.)