Another week, time for another Perl Weekly Challenge!
Task 1: Unique Sum
You are given an array of integers.
Write a script to find out the sum of unique elements in the given array.
Example 1
Input: @int = (2, 1, 3, 2)
Output: 4
In the given array we have 2 unique elements (1, 3).
Example 2
Input: @int = (1, 1, 1, 1)
Output: 0
In the given array no unique element found.
Example 3
Input: @int = (2, 1, 3, 4)
Output: 10
In the given array every element is unique.
The examples make what this challenge is looking for pretty clear. We find the unique elements in the array, and sum those up. I immediately thought of using a hash to accomplish the task:
# find the unique elements
my %unique;
foreach my $int ( @ints ) {
$unique{$int}++;
}
# make a list of ONLY the unique ints
my @unique_ints = grep { $unique{$_} == 1 } @ints;
It’s a common use-case in Perl to use a hash to count how many times something occurs, whether it’s to only do something once or to actually count up occurrences.
I guess I could have populated the %unique
hash via a map
, but I wanted to keep what the code was doing obvious, and sometimes I think using a map
just to execute code in the code block and not to return an array/hash can be confusing.
map { $unique{$_}++ } @ints;
The other thing I knew I wanted to do was show off some List::Util
functions
use List::Util qw( sum );
# sum the unique elements
my $sum = sum(@unique_ints) // 0;
Sure, it would be easy enough to say
my $sum = 0;
foreach my $int ( @unique_ints ) {
$sum += $int;
}
But sum
makes it is a lot shorter. So here’s the entire script…
#!/usr/bin/env perl
use v5.38;
use List::Util qw( sum );
# just accept the list of integers on the command line
my @ints = @ARGV;
# find the unique elements
my %unique;
foreach my $int ( @ints ) {
$unique{$int}++;
}
# make a list of ONLY the unique ints
my @unique_ints = grep { $unique{$_} == 1 } @ints;
# sum the unique elements
my $sum = sum(@unique_ints) // 0;
# produce the output
say "Input: \@int = (" . join(', ', @ints) . ")";
say "Output: $sum";
say "";
print "In the given array ";
if ( scalar(@unique_ints) == scalar(@ints) ) {
say "every element is unique.";
}
elsif ( scalar(@unique_ints) == 0 ) {
say "no unique element found.";
}
else {
say "we have " . scalar(@unique_ints) . " unique elements ("
. join(', ', @unique_ints) . ").";
}
As always, I started with my Perl script and made changes to make it valid Raku:
#!/usr/bin/env raku
use v6;
# just accept the list of integers on the command line
my @ints = @*ARGS;
# find the unique elements
my %unique;
for @ints -> $int {
%unique{$int}++;
}
# make a list of ONLY the unique ints
my @unique_ints = grep { %unique{$_} == 1 }, @ints;
# sum the unique elements
my $sum = [+] @unique_ints;
# produce the output
say "Input: \@int = (" ~ @ints.join(', ') ~ ")";
say "Output: $sum";
say "";
print "In the given array ";
if ( @unique_ints.elems == @ints.elems ) {
say "every element is unique.";
}
elsif ( @unique_ints.elems == 0 ) {
say "no unique element found.";
}
else {
say "we have " ~ @unique_ints.elems ~ " unique elements ("
~ @unique_ints.join(', ') ~ ").";
}
Now, the big decision I had to make was how to do the sum. I picked showing off Raku’s Reduction Metaoperator: [ ]
. When you put an operator between square brackets and put that in front of a Raku Positional (like an Array), it turns the Positional into a single value by applying the operator to the first two elements, and then applying the operator to the result and the next element, and so on until the Positional has run out of elements. You can multiply all the elements of a Positional using [*]
, you can concatenate all the elements of a Positional using [~]
, There’s even a max infix operator that given two operands will return the larger of the two, and this can be applied to a Positional to find the largest value using [max]
.
But I could have used the .sum
routine provided by Raku’s List class (which Arrays are a subclass of):
my $sum = @unique_ints.sum;
Task 2: Empty Array
You are given an array of integers in which all elements are unique.
Write a script to perform the following operations until the array is empty and return the total count of operations.
If the first element is the smallest then remove it otherwise move it to the end.
Example 1
Input: @int = (3, 4, 2)
Output: 5
Operation 1: move 3 to the end: (4, 2, 3)
Operation 2: move 4 to the end: (2, 3, 4)
Operation 3: remove element 2: (3, 4)
Operation 4: remove element 3: (4)
Operation 5: remove element 4: ()
Example 2
Input: @int = (1, 2, 3)
Output: 3
Operation 1: remove element 1: (2, 3)
Operation 2: remove element 2: (3)
Operation 3: remove element 3: ()
This time, the List::Util
function I wanted to use was min
:
#!/usr/bin/env perl
use v5.38;
use List::Util qw( min );
# just accept the list of integers on the command line
my @ints = @ARGV;
my @operations;
my $count = 1;
while ( scalar(@ints) > 0 ) {
my $min = min @ints;
# in either case, we're removing the first element from the list
my $first = shift @ints;
if ($min == $first) {
# the first element is the minimum, discard it
push @operations, "Operation $count: "
. "remove element $min: ("
. join(',', @ints) . ")";
}
else {
# the first element is NOT the minimum, add it to the end
push @ints, $first;
push @operations, "Operation $count: "
. "move $first to the end: ("
. join(',', @ints) . ")";
}
$count++;
}
# produce the output
# let's use @ARGV again, since we modify @ints as we go along
say "Input: \@int = (" . join(', ', @ARGV) . ")";
say "Output: " . scalar(@operations);
say "";
say join "\n", @operations;
This also does an excellent job of showing off array operations: shift
to remove the first element of an array, and push
to append an element to the end of an array (though, I will admit I really like the way PHP allows you to append to the end of an array: $ints[] = $first
).
At first, I was using $ints[0]
to examine the first element in the array and then using shift
to remove it and discard the value if the first element was the minimum value, and if it wasn’t, using shift
to remove the first value and save itm like this:
if ($min == $ints[0]) {
shift @ints;
push @operations, ...;
}
else {
my $first = shift @ints;
push @operations, ...;
}
But then I realized that I was shift
-ing the value off @ints
in either case, and it would just be cleaner to do it before the comparison so I could use $first
instead of $ints[0]
.
The Raku version is nothing fancy this time:
#!/usr/bin/env raku
use v6;
# just accept the list of integers on the command line
my @ints = @*ARGS;
my @operations;
my $count = 1;
while ( @ints.elems > 0 ) {
my $min = @ints.min;
# in either case, we're removing the first element
# from the list
my $first = @ints.shift;
if ($min == $first) {
# the first element is the minimum, discard it
push @operations, "Operation $count: "
~ "remove element $min: ("
~ @ints.join(', ') ~ ")";
}
else {
# the first element is NOT the minimum, add it to the end
push @ints, $first;
push @operations, "Operation $count: "
~ "move $first to the end: ("
~ @ints.join(', ') ~ ")";
}
$count++;
}
# produce the output
# let's use @ARGV again, since we modofy @ints as we go along
say "Input: \@int = (" ~ @*ARGS.join(', ') ~ ")";
say "Output: " ~ @operations.elems;
say "";
say join "\n", @operations;