Perl Weekly Challenge 98
This week’s solutions explore some more obscure parts of the Raku ecosystem, namely state variables and adverbs.
Task 1: Read N-characters
You are given file $FILE
.
Create subroutine readN($FILE, $number)
that 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"
Solution
See below for explanation and any implementation-specific comments.
sub readN(Str $file-name, Int $chars-to-read where $chars-to-read > 0) returns Str {
state %file-map; # [1]
if %file-map{$file-name}:!exists { # [2]
%file-map{$file-name} = IO::CatHandle.new($file-name) # [3]
}
%file-map{$file-name}.readchars($chars-to-read); # [4]
}
sub MAIN(Str $FILE, Int $N) {
say readN($FILE, $N);
say readN($FILE, $N);
say readN($FILE, $N);
}
This program runs as such:
# Assuming input.txt contains '1234567890'
$ raku ch-1.raku input.txt 4
1234
5678
90
Explanation
All of this relies on two very cool aspects of Raku: state variables and CatHandles
, which I will detail below. Basically, we just create a new CatHandle
and pass all the heavy lifting to it via readchars
; it keeps track of where it is in the file and will not attempt to read past the end of the file.
Specific comments
- A state variable in Raku is similar to a static variable in other languages, with the additional caveat that it can be instantiated in subroutines in addition to classes. This variable will be instantiated once and shared across all invocations of the subroutine. So, if we wanted to, we could do the following:
# input1.txt
# maryalamb
# input2.txt
# hadlittle
sub MAIN {
my $input1 = 'input1.txt';
my $input2 = 'input2.txt';
say readN($input1, 4);
say readN($input2, 3);
say readN($input1, 1);
say readN($input2, 6);
say readN($input1, 4);
}
# Output:
# mary
# had
# a
# little
# lamb
- This is a special case of the
exists
adverb. Adverbs are just named arguments. In this case:exists
is essentially the pairexists => True
, so to negate it we use!:exists
. I found this pretty interesting, because I thought it would have been!%file-map{$file-name}:exists
, but Raku complains about that. - A
CatHandle
is normally used tocat
together several file handles and read them all at once. In this case, we are using it to take advantage of itsreadchars
function discussed below. readchars
essentially just tracks our offset in the given handle and always outputs the nextn
characters that we request, and just returns an empty string if we are at the end of the file. Super convenient for this challenge!
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.
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.
Solution
See below for explanation and any implementation-specific comments.
sub challenge(@N is copy, Int $N) returns Int { # [1]
my @new = $N ∉ @N ?? (|@N, $N).sort !! @N; # [2]
@new.first($N, :k); # [3]
}
sub MAIN(Int $N, *@N where all(@N) ~~ Int) {
say challenge(@N, $N)
}
This program runs as such:
# $N followed by @N
$ raku ch-2.raku 3 1 2 3 4
2
Explanation
This is pretty straight forward; we check if $N
is in @N
(if not, we add it and re-sort), then return the index of $N
. That’s it!
Specific Comments
- In Raku, the sigil (
$
,@
,%
, etc.) is part of the variable declaration. Because of that, for better or for worse, it is not a problem (as far as the compiler is concerned) that we have to variables namedN
. - Rather than mark
@N
asis copy
so we could use@N.push($N).sort
, since we assign it to a new variable anyway (@new
), we can use a slip to append$N
to@N
. first
returns the first instance of the argument in the list (in this case$N
). We supply the:k
adverb to have it return the index of$N
rather than$N
itself.
Final Thoughts
Raku has a lot of cool stuff built in to it. I find the hardest part of navigating this language is the documentation. Things are mostly well documented, but things like Stack Overflow posts and how-tos can be few and far between. Hopefully this blog is helpful for anyone trying to learn the language!