Perl weekly challenge 98

Here are solutions to this weeks challenges from the Perl Weekly Challenge.

You can find my full code on Github

Challenge 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

Rather than turning this into an object which was the first idea - I decided to keep the code clean by making it a function call, and to also make it work with multiple file handles open simultaneously.

To achieve this without an object - we will need to use a global hash to contain the opened file handles - so that when we re-call the function we don't re-open the file.

Aside - note if we were only reading these files once in a while - an alternative approach would be to keep an array of file-positions rather than file-handles, and then at each invocation - re-open the file - seek to the location and return the bytes before closing it again - this would cut down the resources associated with the script.

We could use a state variable declaration here {but that would mean that we couldn't add extra code to be able to clean up data later}.

In our function we use one of the "lower-level" file functions, read, which reads a given number of bytes into a scalar variable - note it has to be initialised before the function call.

If read returns "false" then that means that there was no content in the file at that point.


sub readN {
  my( $fn, $bytes ) = @_;

  ## Create a file handle if we don't already have one
  unless( exists $handles{$fn} ) {
    open $handles{$fn}, '<', $fn;
  }

  ## Create a buffer for the return value
  my $t = '';

  ## Use "read" to read the $bytes bytes - these are put into 2nd parameter
  ## If read returns undef it means it has reached the end of the file...
  warn "Reached end of file $fn\n" unless read ${$handles{$fn}}, $t, $bytes;

  ## Return string
  return $t;
}

The additional cleanup code is used to forcibly close the file handles before the end of the script - this is why we could not rely on using state variables.


sub cleanup {
  ## For neatness close all handles
  ## delete returns the value of the has deleted
  ## if filenames are passed then only those are cleaned up
  close delete $handles{$_} foreach @_ ? grep { exists $handles{$_} } @_ : keys %handles;
}

sub show_open {
  ## Return a list of open filenames
  return keys %handles;
}

Challenge 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

This is a much simpler problem than the previous one - we have to find the index of the number (or where to insert it)

Once we know where that is we check to see if we need to insert or not (using the 4 parameter version of splice) and to return the value.


sub insert_pos {
  my( $t, $l, $val ) = (0,@_);

  ## Repeat unless we have got to end of list or the new entry is greater than val
  $t++ while $t < @{$l} && $l->[$t] < $val;

  ## If we are after the end of the list (to avoid warning) OR
  ## If we haven't found the entry then we use splice to insert it
  splice @{$l},$t,0,$val if $t == @{$l} || $l->[$t] != $val;

    ## Warn to show splice has worked...
    warn ">> $t ( @{$l} )\n"; ## Demonstrate splice

  ## Return the index of the number!
  return $t;
}

If we aren't interested in doing the actual insert into the list then we can avoid the splice and so the code reduces to this.


sub insert_pos {
  my( $t, $l, $val ) = (0,@_);
  $t++ while $t < @{$l} && $l->[$t] < $val;
  return $t;
}

You could easily run this code with simpler function...


sub insert_pos {
  my( $l, $val ) = @_;
  return scalar grep { $_ < $val } @{$l};
}

But using our own loop is "optimal" if the number being inserted is near the start of the list - as the numbers increase numerically then we only need to check up to (or as far) as the number itself rather than the whole list. {you could use firstidx from List::MoreUtils}

2 Comments

we will need to use a global hash to contain the opened file handles

The state keyword means that this doesn't need to be a global hash.

Leave a comment

About James Curtis-Smith

user-pic Perl developer for nearly 30 years now, mainly in maintenance scripts and web pages, using mod_perl.