I’ve been doing the Perl Weekly
Challenges. The
latest
involved reading tiny chunks from a file and searching a list.
(Note that this is open until 7 February 2021.)
TASK #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.
Task 1 is usually the easier of the two, and indeed this would be
fairly trivial, except for the idea that the sub should take the
filename as a parameter. Which means that somewhere we need to keep
track of how much has already been read, either using an open file
handle or with an internal counter; I chose the file handle. But this
is a really ugly way of doing things; an object would be much more
usual, which you'd initialise with the filename. (Indeed, you might
call it "a file handle" since that's how they work.)
In Perl I can define the static variables in the same block as the
sub
, so that's easy.
{
my $fh;
my $fn='';
sub readN {
my $fnn=shift;
my $siz=shift;
if ($fnn ne $fn) {
$fn=$fnn;
if (defined $fh) {
close $fh;
undef $fh;
}
}
if (!defined $fh) {
open $fh,'<',$fn;
}
my $buf;
my $l=read ($fh,$buf,$siz);
if ($l < $siz) {
close $fh;
undef $fh;
$fn='';
}
return $buf;
}
}
And Raku works similarly, though with state variables (Perl5 has these
too, I just didn't use them).
sub readN($fnn,$siz) {
state $fh=0;
state $fn='';
if ($fnn ne $fn) {
$fn=$fnn;
if ($fh) {
$fh.close;
$fh=0;
}
}
unless ($fh) {
$fh=$fn.IO.open;
}
my $buf=$fh.read($siz).decode;
if ($buf.chars < $siz) {
$fh.close;
$fh=0;
$fn='';
}
return $buf;
}
In Python and Ruby I used a global hash table of filename to handle.
rn=dict()
def readN(fn,n):
if fn in rn:
fh=rn[fn]
else:
fh=io.FileIO(fn)
rn[fn]=fh
buf=fh.read(n)
if len(buf)==0:
fh.close
del rn[fn]
return buf
and
$rn=Hash.new
def readN(fn,siz)
if $rn.has_key?(fn) then
fh=$rn[fn]
else
fh=File.new(fn,'r')
$rn[fn]=fh
end
buf=fh.read(siz)
if fh.eof? then
fh.close
$rn.delete(fn)
end
return buf
end
and in Rust, after a lot of back-and-forth with unsafe code, I just
gave up and made an object out of it the way I would in the real
world.
pub struct Reader {
handle: std::fs::File
}
impl Reader {
pub fn new(fname: &str) -> Reader {
let fna=fname.to_string();
let fh=std::fs::File::open(fna).unwrap();
Reader { handle: fh }
}
#[allow(non_snake_case)]
(this to suppress the warning about the capitalised subroutine name)
pub fn readN(&mut self, siz: usize) -> String {
let mut buf=vec![0u8; siz];
let mut taker=self.handle.borrow().take(siz as u64);
let t=taker.read(&mut buf).unwrap();
let q=&buf[0..t];
let s=str::from_utf8(&q).unwrap();
return s.to_string();
}
}
TASK #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.
I skipped the "place the target in the sorted array" bit since the
array is immediately thrown away at function exit…
In the real world at this scale I would normally just transmute the
array to a set to test for presence (caution, untested code):
my %n=map {$_ => 1} @N;
if (exists $n{$N}) {
return scalar @N;
}
Then count the smaller entries:
my @t=grep {$_ < $N} @N;
return scalar @t;
which doesn't even need the array to be sorted, but this is clearly a
computer science homework problem so I'll try to solve it like one
with a binary search.
sub sip {
my $n=shift;
my $t=shift;
if ($n->[-1] < $t) {
return scalar @{$n};
}
my ($l,$h)=(0,$#{$n});
while ($h-$l > 1) {
my $m=int(($h+$l)/2);
if ($n->[$m] == $t) {
return $m;
} elsif ($n->[$m] > $t) {
$h=$m;
} else {
$l=$m;
}
}
if ($n->[$l] >= $t) {
return $l;
}
return $h;
}
And the other languages are basically identical.
Full code on
github.
Comments on this post are now closed. If you have particular grounds for adding a late comment, comment on a more recent post quoting the URL of this one.