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