Perl Weekly Challenge: Week 283

Challenge 1:

Unique Number

You are given an array of integers, @ints, where every elements appears more than once except one element.

Write a script to find the one element that appears exactly one time.

Example 1
Input: @ints = (3, 3, 1)
Output: 1
Example 2
Input: @ints = (3, 2, 4, 2, 4)
Output: 3
Example 3
Input: @ints = (1)
Output: 1
Example 4
Input: @ints = (4, 3, 1, 1, 1, 4)
Output: 3

In Raku we can solve this as a one-liner.

@*ARGS.classify({$_}).sort({$^a.value.elems <=> $^b.value.elems})[0].key.say

(Full code on Github.)

The command-line arguments are turned, using .classify() into a hash whose keys are unique integers in the input and whose values are the individual instances of those integers. This hash is then .sort()ed so that keys with a shorter list of values come before keys with longer ones. The element of the sorted hash, [0], has the key extracted from it with .key() and this is printed out with .say().

This is the Perl version.

my @ints = @ARGV;

It is longer because we are missing Rakus' .classify(). We have to build up the hash with a for-loop like this:

my %count;

for my $int (@ints) {
    $count{$int}++;
}

Getting the first element of the sorted list was a little awkward and the whole line is not as readable as Raku IMO.

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

(Full code on Github.)

Challenge 2:

Digit Count Value

You are given an array of positive integers, @ints.

Write a script to return true if for every index i in the range 0 <= i < size of array, the digit i occurs exactly the $ints[$i] times in the given array otherwise return false.

Example 1
Input: @ints = (1, 2, 1, 0)
Ouput: true

$ints[0] = 1, the digit 0 occurs exactly 1 time.
$ints[1] = 2, the digit 1 occurs exactly 2 times.
$ints[2] = 1, the digit 2 occurs exactly 1 time.
$ints[3] = 0, the digit 3 occurs 0 time.
Example 2
Input: @ints = (0, 3, 0)
Ouput: false

$ints[0] = 0, the digit 0 occurs 2 times rather than 0 time.
$ints[1] = 3, the digit 1 occurs 0 time rather than 3 times.
$ints[2] = 0, the digit 2 occurs exactly 0 time.

First we set up storage for the result and make its' initial value True.

my $result = True;

Once again we can use .classify() to solve this task.

@ints.classify({ $_; }, :into( my %count) );

For each of the indices of @ints...

for @ints.keys -> $n {

...if there are not as many instances of the integer represented by that index as the element itself we set the result to False and exit the loop.

One small stumbling block is that if the hash key is empty (i.e. no instances of that integer were found,) its' value will not be defined which will cause .elems() to fail. So I used the defined-or operator // to explicitly provide an empty list in that case.

    if (%count{$n} // []).elems != @ints[$n] {
        $result = False;
        last;
    }
}

Finally, after all the indices have been examined, we print the result.

say $result;

(Full code on Github.)

Translating the Raku version to Perl gave me only one problem.

my @ints = @ARGV;
my $result = 'true';
my %count;

for my $int (@ints) {
    $count{$int}++;
}

for my $n (keys @ints) {

When trying to replicate the empty key workaround I used in Raku, I found that scalar [] gives the address of an array reference instead of a count of 0. Also no good, scalar () which Perl mistakes for a function call. I could have used scalar @{[]} which dereferences an array reference which is then correctly counted as a 0-length array but this seemed unnecessarily complicated so I just used 0. scalar 0 returns 0.

    if (scalar ($count{$n} // 0) != $ints[$n]) {
        $result = 'false';
        last;
    }
}

say $result;

(Full code on Github.)