RabbitFarm

2021-02-06

Perl Weekly Challenge 098

The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

Part 1

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.

Solution


use strict;
use warnings;
sub read_maker0{
    my $n = 0;
    return sub{
        my($file, $x) = @_;
        my $chars;
        open(FILE, $file);
        unless(seek(FILE, $n, 0)){
            close(FILE);
        }
        read(FILE, $chars, $x);
        $n = $n + $x;
        return $chars;
    }
}

sub read_maker1{
    my ($file) = @_;
    my $n = 0;
    open(FILE, $file);
    return sub{
        my($x) = @_;
        my $chars;
        my $read = read(FILE, $chars, $x);
        $n = $n + $x;
        unless(seek(FILE, $n, 0)){
            close(FILE);
        }
        return $chars;
    }
}

MAIN:{
    my($FILE, $number) = ("ch-1.dat", 4);
    my($read_n, $chars);
    $read_n = read_maker0();
    $chars = $read_n->($FILE, $number);
    print "$chars\n";
    $chars = $read_n->($FILE, $number);
    print "$chars\n";
    $chars = $read_n->($FILE, $number);
    print "$chars\n";
    
    
    $read_n = read_maker1($FILE);
    $chars = $read_n->($number);
    print "$chars\n";
    $chars = $read_n->($number);
    print "$chars\n";
    $chars = $read_n->($number);
    print "$chars\n";
}

Sample Run


$ perl perl/ch-1.pl
1234
5678
90

1234
5678
90

Notes

I actually did this two different ways. The first follows the letter of the challenge as to the parameters of the read_n function and the second differs, only passing in $number and does not include the filename.

Before I get into the differences it makes sense to point out how read_maker0() works. What is does is create a closure over the value $n which will hold the current position in the file. Think of the variable $n created in read_maker0() as captured inside the function that is being returned. This process is called currying and it’s a neat trick. I’ve used it in the past for these challenges, the first being way back in Challenge 003! In this way read_maker0() is creating the function which we are referring to by the scalar $read_n.

After each read $n is incremented and used to seek to the next position. I should note that this is not really necessary here since the value of $number is never changed. In this case the read alone will advance the file position as necessary. However, by including seek the solution is more general. We would be able to move around the file however we want, backwards and forwards, with seek if we wanted to.

So we see that we can capture $n and use it to store the file position between function calls. The challenge states that we are to called read_n with two parameters, the filename and the number of characters to read. As you can see, we do not need to keep sending the filename with each function call. The filename can also be a part of the closure!

That is the difference between read_maker0() and read_maker1(). The first returns a read_n function that matches the challenge specification of taking a filename and a number of characters to read. read_maker1() returns a function that only takes the number of characters to read, the function itself has a stored value for the file handle we want.

One small final thing to mention: anyone unfamiliar with read might notice that there is no checking to see if we attempt to read past the end of the file. That is because read will read all the characters it can and if it hits the end of the file it will stop. The return value from read is the number of characters successfully read. While we do not check that value in this code, if we did we would see that in this example the final read would return 2, which is clear in the output shown.

Part 2

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.

Solution


use strict;
use warnings;

sub find_insert{
    my($list, $n) = @_;
    if($n < $list->[0]){
        unshift @{$list}, $n;
        return 0;
    }
    if($n > $list->[@{$list} - 1]){
        push @{$list}, $n;
        return @{$list} - 1;
    }
    for(my $i = 0; $i < (@{$list} - 1); $i++){
        return $i if $n == $list->[$i];
        if($n > $list->[$i] && $n < $list->[$i + 1]){
            splice(@{$list}, $i, 2, ($list->[$i], $n, $list->[$i + 1]));
            return $i + 1;
        }
    }
}


MAIN:{
    my(@N, $N, $i);
    @N = (1, 2, 3, 4);
    $N = 3;
    $i = find_insert(\@N, $N);
    print "$i\n"; 
    
    @N = (1, 3, 5, 7);
    $N = 6;
    $i = find_insert(\@N, $N);
    print "$i\n"; 
    
    @N = (12, 14, 16, 18);
    $N = 10;
    $i = find_insert(\@N, $N);
    print "$i\n"; 
    
    @N = (11, 13, 15, 17);
    $N = 19;
    $i = find_insert(\@N, $N);
    print "$i\n"; 
}

Sample Run


$ perl perl/ch-2.pl
2
3
0
4

Notes

While somewhat convoluted sounding at first this part of Challenge 098 ended up being fairly straightforward, especially when using splice to do any array insertions. Probably there are more “fun” ways to have done this but the intuitive way here has an almost comforting look to it. Reminds me of university computer lab exercises!

Anyway, the approach here is to consider the possible cases. find_insert starts off by checking to see if $n would belong at the very start or end of the array. If neither of those cases hold we loop over the array looking for where $n might be. If found in the array we return with the index, else we insert with splice.

The challenge never asks to see the modified array so I suppose it is possible to merely return where $n belongs without actually inserting it but that didn’t seem quite as sporting.

References

Challenge 098

Perlmonks article on currying

posted at: 21:34 by: Adam Russell | path: /perl | permanent link to this entry