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
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];
Challenge 2:
Digit Count Value
You are given an array of positive integers,
@ints
.Write a script to return
true
if for every indexi
in the range0 <= i < size of array
, the digiti
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;
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;