Perl Weekly Challenge: Week 142

My attempt at doing the Advent of Code in Raku continues. There have been a couple of dicey days but so far I am managing to keep up. You can see my solutions on github in the Advent of Raku 2021 repository. (That reminds me I haven't uploaded the last few days; it will probably be done by the time you read this.)

Challenge 1:

Divisor Last Digit

You are given positive integers, $m and $n.

Write a script to find total count of divisors of $m having last digit $n.

Example
Input: $m = 24, $n = 2
Output: 2

The divisors of 24 are 1, 2, 3, 4, 6, 8 and 12.
There are only 2 divisors having last digit 2 are 2 and 12.
Example 2
Input: $m = 30, $n = 5
Output: 2

The divisors of 30 are 1, 2, 3, 5, 6, 10 and 15.
There are only 2 divisors having last digit 5 are 5 and 15.

This is very similar to challenge 1 last week. In fact, we can simplify the code used there to make this a one-liner. Taking the first two command line arguments as the equivalent of $m and $n, we first .grep() through @*ARGS[0] for divisors. Unlike last week, the number itself is not counted as a divisor but 1 is. Then we .grep() through those divisors for oned that end in @*ARGS[1]. These in turn are counted and the result printed.

(1 ..^ @*ARGS[0]).grep({ @*ARGS[0] %% $_; }).grep({ $_.match(/ "@*ARGS[1]" $ /)}).elems.say;

(Full code on Github.)

And here it is in Perl:

say scalar grep { $ARGV[0] % $_ == 0 && $_ =~ /$ARGV[1]$/ } 1 .. $ARGV[0] - 1;

(Full code on Github.)

This time I chose to combine the two grep()s into one. There doesn't seem to be a noticable speed difference to either way. Probably Perl and Raku are smart enough to optimize this or perhaps the effect can only be seen with really huge numbers which have many divisors.

Challenge 2:

Sleep Sort

Another joke sort similar to JortSort suggested by champion Adam Russell.

You are given a list of numbers.

Write a script to implement Sleep Sort. For more information, please checkout this post.

I must say the idea of this is brilliant if not entirely practical. I haven't used threads before in either Perl or Raku so it took me a while to get this working properly. First, here is my Raku version.

This function is run by each thread. It sleeps for a certain amount of time proportional to the value of $arg (which therefore has to be numeric. If we wanted to sort e.g. strings, we would have to find some way of converting them into a numeric value.) After, it has woken up, the value of $arg is printed.

I had to experiment quite a bit to get an optimal scaling factor for sleep. Its' parameter is the number of seconds to sleep which would have been very slow. Thousandths of a second is more precision than sleep is capable of atleast on my computer and results in unreliable times for the threads which then give the results out of order. Hundredths of a second seemed like a happy medium so I divide $arg by 100.

sub work($arg) {
    sleep $arg / 100;
    say $arg;
}

MAIN() collects arguments from the command line.

sub MAIN(*@ARGS) {

For each argument, a thread is created using Thread.start() that runs work() with that argument. These threads are added into an array.

    my @threads;
    for @*ARGS -> $arg {
        @threads.push( Thread.start( &{work($arg)} ) );
    }

The .finish() method actually runs the thread to completion so it is called for each thread in our array.

    .finish for @threads;
}

(Full code on Github.)

The result is the numerically sorted arguments. Like I said, brilliant!

In perl, you need the threads module which was not immediately obvious to me. I found out about it from the Tutorial on threads in Perl which is in the obscurely-named perlthrtut perldoc page. Really would it be a hardship to add 'ead' in there?

use threads;

Anway this is how work() is implemented. I had no luck at all with Perls' version of sleep() so I did something I often do in real work but very rarely in these challenges and reached for an external module, namely Time::HiRes and its usleep() function. This is reliable but measures in millionths of a second so I had to multiply $arg by 10,000 in order to get hundredths like the Raku version.

use Time::HiRes qw / usleep /;

sub work {
    my ($arg) = @_;

    usleep $arg * 10_000;
    say $arg;
}

create() is the equivalent of start() in Raku for creating a thread.

my @threads = map { threads->create(\&work, $_); } @ARGV; 

join() is the equivalent of finish(). It also runs the thread to completion.

foreach (@threads) {
    $_->join;
}

(Full code on Github.)