Perl Weekly Challenge: Week 195

Once again I should thank Mohammad for not making the challenges too hard this week because Advent of Code is kicking my backside. Still soldiering on though...

Challenge 1:

Special Integers

You are given a positive integer, $n > 0.

Write a script to print the count of all special integers between 1 and $n.

An integer is special when all of its digits are unique.

Example 1
Input: $n = 15
Output: 14 as except 11 all other integers between 1 and 15 are spcial.
Example 2
Input: $n = 35
Output: 32 as except 11, 22, 33 all others are special.

We can write this in Raku as a one-liner. Split into its' components...

For the range of numbers between 1 and the target which we get from the command line:

(1 .. @*ARGS[0])

...We split each number into its constituent digits. We use .unique() to find the list of unique digits and compare them to the original list of digits. If both lists are of the same length, we have a match:

.grep({ my @digits = $_.comb; @digits ~~ @digits.unique; })

...Then we count how many matches we got:

.elems

...and print the result.

.say;

(Full code on Github.)

Alas, we cannot quite get to a one-liner in Perl as we need to provide our own unique() function. Fortunately, I had already written one. With this, the Perl version also manages to be quite concise.

say scalar grep { my @digits = split //, $_; scalar @digits == scalar unique(\@digits); } 1 .. $n;

(Full code on Github.)

Challenge 2:

Most Frequent Even

You are given a list of numbers, @list.

Write a script to find most frequent even numbers in the list. In case you get more than one even numbers then return the smallest even integer. For all other case, return -1.

Example 1
Input: @list = (1,1,2,6,2)
Output: 2 as there are only 2 even numbers 2 and 6 and of those 2 appears the most.
Example 2
Input: @list = (1,3,5,7)
Output: -1 since no even numbers found in the list
Example 3
Input: @list = (6,4,4,6,1)
Output: 4 since there are only two even numbers 4 and 6. They both appears the equal number of times, so pick the smallest.

Here's another example of how you can easily chain together Raku methods to solve a problem in an efficient and concise way.

Given the list of numbers:

@list

...first we remove all the odd numbers:

.grep({ $_ % 2 != 1; })

...then we use .classify() to create an anonymous hash where the keys are numbers that occur in @list and the values are the occurrences of each number:

.classify({ $_ })

... now we sort this hash on two levels. First by the number of times a number occurred, largest amount to smallest. Because the values of the hash are occurrences, we need to tack .elems on the end to get the number of occurrences. The second level is to sort by the numbers themselves (the keys of the hash) from smallest to largest:

.sort({$^b.value.elems <=> $^a.value.elems || $^a.key <=> $^b.key})

Now we take the first element of the sorted hash. It will be a key-value pair:

.first

...We are only interested in the key which will be the result the spec asked for, the smallest even integer:

.key

In the event the above steps were not enough to be able to produce a smallest even integer, the result becomes -1 instead:

|| -1;

(Full code on Github.)

In the actual script, there is a say() in front of all this so the result is printed to the screen.

This is the Perl equivalent of the code above.

Several operations that were discrete steps in the Raku version are combined in Perl. For instance, instead of .classify(), we go through @list (with odd values filtered out) and populate a hash with each element one by one. We don't need to store the occurrences, we can include the count directly in the hash.

my %count;

for my $elem (grep { $_ % 2 != 1; } @list) {
    $count{$elem}++;
}

say [sort { scalar $count{$b} <=> scalar $count{$a} || $a <=> $b } keys %count]->[0] || -1;

(Full code on Github.)