Perl Weekly Challenge 74: counting
One way to let me improve my knowledge about Raku (aka Perl 6) is to implement programs in it. Unluckily, I don’t have any production code to implement in Raku yet (sob!). So, why not try solving the Perl Weekly Challenge tasks?In the following, the assigned tasks for Challenge 74.
PWC 74 - Task 1
The first task was about finding the element in array that appears more than the half size of the array. It has been quite simple to implement it:sub MAIN( *@array where { @array.grep( * ~~ Int ).elems == @array.elems } ) {
my $N = @array.elems;
my $majority = floor( $N / 2 );
my %counting;
%counting{ $_ }++ for @array;
given %counting.pairs.map( { .value >= $majority ?? $_ !! Nil } ).grep( * ~~ Pair ).unique.head {
when .so { .key.say; }
default { '-1'.say; }
}
}
First of all, I ensure that all the parameters provided to the script are integers (since slurpy array cannot be typed, I need to ensure it with an awkward
grep
).
The
$majority
variable holds the compute majority value, as by the problem defintion. It is there just to make the code a little cleaner.
The
%counting
hash simply counts every number in the array how many times it appears.
Then I use a
given
to simplify the long Pair
extraction: for every Pair
in the array, I map
it to a list where every element is the Pair
itself only if the value
(i.e., the number of occurrences) is greater than $majority
, otherwise I place a Nil
placeholder. This gives me a list that is made by pairs and nulls, so I grep
out everything is not a Pair
, and get the first unique value in the list (there could be more than one value that satisfy the problem requirement!).
If what I get is a
Pair
, as expected, I print the key
, that is the number in the array. Otherwise, if there is no Pair
at all, I print the special value -1
.
PWC 74 - Task 2
I spent much more time trying to figure out what the problem was asking me to do than to implement it. I was stucked at a point where I was able to run one of the test but not the other or viceversa. However, the implementation I wrote is:sub MAIN( Str $S where { $S.chars > 2 } ) {
my @result;
my %counting;
my @chars = $S.comb( '', :skip-empty );
for 0 ..^ @chars.elems -> $index {
my $current-char = @chars[ $index ];
# if the result array is empty
# or the char is not in the array, it is
# ok to push
@result.push( $current-char ) && next if ! @result || ! @result.grep( * ~~ $current-char );
# if here I need to search for the first rightmost
# not repeating char so far
%counting = %();
%counting{ $_ }++ for $S.substr( 0 .. $index ).comb( '', :skip-empty );
my $fnr = $S.substr( 0 .. $index )
.comb( '', :skip-empty )
.reverse
.grep( { %counting{ $_ }:exists && %counting{ $_ } == 1 } )
.first // '#';
@result.push: $fnr;
}
@result.join.say;
}
The idea is simple: I split the string into single chars that I store in the
@chars
array, and then I loop other the array. Since I need to slice the array or substring, I use numberical indexes, and $current-char
is what I’m on at every loop interation.
The easy case is when the array of
@result
is empty (first iteration of the loop) or the char does not appear at all in the array: in such case I can insert the char and restart the loop from the next
iteration.
The hard case is when the
$current-char
is already in the @result
array: here I need to find out the first non repeating char. The non repeating char must be searched in the substring of the original input string up to the current char, that is $S.substr( 0 .. $index )
. I split the string into single chars and use the %counting
hash to count every occurrency. Now I consider again the substring, I comb
it into single chars, I reverse them to the the rightmost char first, then I grep
the resulting list searching for all the characters that appear exactly one time, and then I take the first
element. If nothing like that exists, the special char #
is used.
It is possible to remove the
grep
intermediate list extraction, since first
accepts a callable to use as filter:
my $fnr = $S.substr( 0 .. $index )
.comb( '', :skip-empty )
.reverse
.first( { %counting{ $_ }:exists && %counting{ $_ } == 1 } )
// '#';
Similarly, it is possible to remove the
reverse
too, since first
can veing its search from the end of the list with the adverb :end
:
my $fnr = $S.substr( 0 .. $index )
.comb( '', :skip-empty )
.first( { %counting{ $_ }:exists && %counting{ $_ } == 1 }, :end )
// '#';