Perl Weekly Challenge 98.

My solutions (task 1 and task 2) to the The Weekly Challenge - 098.

Task 1: Read N characters

Submitted by: Mohammad S Anwar 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 start loading a couple of useful packages.

# Perl weekly challenge 098
# Task 1: Read N characters
#
# See https://wlmb.github.io/2021/02/01/PWC098/#task-1-read-n-characters
use warnings;
use strict;
use v5.12;
use Scalar::Util qw(looks_like_number);
use List::Util qw(all);

The reader has to remember its current position, so I need a different reader for each possible filename. This is a nice opportunity for using functional programming. I make a routine that builds a reader routine for each new filename seen and a dispatcher that calls the appropriate routine to actually read line by line buffering the input.

sub readN { # find and call the appropriate reader with the appropriate argument
    my $filename=shift;
    my $number=shift;
    return reader($filename)->($number); #get the appropriate reader to read
}


sub reader { #returns a reader, maybe creating it.
    state %reader; #hash of readers, one per seen filename
    my $filename=shift;
    $reader{$filename} //=new_reader($filename);
}

sub new_reader {#create a new reader
    my $filename=shift;
    open my $fh, "<", $filename or die "Couldn't open $filename: $!";
    my $line=""; #plays the role of a buffer
    my $reader= sub { #This is the actual reader routine
	my $number=shift;
	while($number>length($line)){ #get enough characters
	    my $nextline=<$fh>;
	    last unless $nextline;
	    #I remove newlines. If not desired, comment the next line
	    chomp($nextline);
	    $line .= $nextline;
	}
	my $result=substr $line,0,$number;
	(substr $line,0,$number)='';
	return $result;
    }
}

To test the code I read the filenames and the amount of characters from @ARGV.

usage() unless @ARGV%2==0;
usage() unless all {looks_like_number($ARGV[$_]) && $ARGV[$_]>=1}, map {2*$_+1} 0..(@ARGV-1)/2;
my $res; # Don't print empty lines after file is consumed
say $res while(@ARGV and $res=readN(shift @ARGV, shift @ARGV));
sub usage {
    say <<'END';
    ./ch-1.pl file1 N1 file2 N2 ...
    Reads N1 chars from file1, N2 from file2 and so on.
    Filenames may repeat
END
}

Example:

echo "1234567890" > "input.txt"
./ch-1.pl input.txt 4 input.txt 4 input.txt 4

Results:

1234
5678
90

Example with alternating files and reading beyond the end:

echo "1234567890" > "numbers.txt"
echo "abcdefghij" > "letters.txt"
./ch-1.pl numbers.txt 4 letters.txt 4 numbers.txt 4 letters.txt 4 \
          numbers.txt 4 letters.txt 4 numbers.txt 4 letters.txt 4

Results:

1234
abcd
5678
efgh
90
ij

Task 2: Search Insert Position

Submitted by: Mohammad S Anwar 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 may be solved using routines from List::Util and List::MoreUtils. I simply add $N to the list, remove it if it already was in, sort the list, in case it is necessary, and find first and only appearance in the resulting list. The full code is thus: say first_index {$_==$N} sort {$a<=>$b} uniq (@N, $N);

Example 1:

perl -MList::Util=uniq -MList::MoreUtils=first_index \
    -E '@N= (1, 2, 3, 4); $N = 3;' \
    -E 'say first_index {$_==$N} sort {$a<=>$b} uniq (@N, $N)'

Results:

2

Example 2:

perl -MList::Util=uniq -MList::MoreUtils=first_index \
    -E '@N= (1, 3, 5, 7); $N = 6;' \
    -E 'say first_index {$_==$N} sort {$a<=>$b} uniq (@N, $N)'

Results:

3

Example 3:

perl -MList::Util=uniq -MList::MoreUtils=first_index \
    -E '@N= (12, 14, 16, 18); $N = 10;' \
    -E 'say first_index {$_==$N} sort {$a<=>$b} uniq (@N, $N)'

Results:

0

Example 4:

perl -MList::Util=uniq -MList::MoreUtils=first_index \
    -E '@N= (11, 13, 15, 17); $N = 19;' \
    -E 'say first_index {$_==$N} sort {$a<=>$b} uniq (@N, $N)'

Results:

4

A full code reading the data ($N, @N) from @ARGV would be

# Perl weekly challenge 098
# Task 2: Search Insert Position
#
# See https://wlmb.github.io/2021/02/01/PWC098/#task-2-search-insert-position
use warnings;
use strict;
use v5.12;
use List::Util qw(uniq);
use List::MoreUtils qw(first_index);

my ($N, @N)=@ARGV;
say "Input: \@N=(", join(", ", @N), ") and \$N=$N\n",
    "Output: ",
    first_index {$_==$N} sort {$a<=>$b} uniq (@N, $N);

Example 1:

./ch-2.pl 3 1 2 3 4

Results:

Input: @N=(1, 2, 3, 4) and $N=3
Output: 2

Example 2:

./ch-2.pl 6 1 3 5 7

Results:

Input: @N=(1, 3, 5, 7) and $N=6
Output: 3

Example 3:

./ch-2.pl 10 12 14 16 18

Results:

Input: @N=(12, 14, 16, 18) and $N=10
Output: 0

Example 4:

./ch-2.pl 19 11 13 15 17

Results:

Input: @N=(11, 13, 15, 17) and $N=19
Output: 4
Written on February 1, 2021