Perl Weekly Challenge: Week 336
Challenge 1:
Equal Group
You are given an array of integers.
Write a script to return true if the given array can be divided into one or more groups: each group must be of the same size as the others, with at least two members, and with all members having the same value.
Example 1
Input: @ints = (1,1,2,2,2,2)
Output: true
Groups: (1,1), (2,2), (2,2)
Example 2
Input: @ints = (1,1,1,2,2,2,3,3)
Output: false
Groups: (1,1,1), (2,2,2), (3,3)
Example 3
Input: @ints = (5,5,5,5,5,5,7,7,7,7,7,7)
Output: true
Groups: (5,5,5,5,5,5), (7,7,7,7,7,7)
Example 4
Input: @ints = (1,2,3,4)
Output: false
Example 5
Input: @ints = (8,8,9,9,10,10,11,11)
Output: true
Groups: (8,8), (9,9), (10,10), (11,11)
Once again, Raku comes through with a one-liner.
First we count the occurrences of each integer in the command-line arguments using the .classify()
method. This create a Hash
(specified by the :into
argument) whose keys are distinct numbers and whose values are the
number of times each occurred. In hindsight I could also have used .Bag
which would have saved a few more characters. Then we find the greatest common divisor (GCD) of all counts (%c.values
) with [gcd]
and if it is greater or equal to 2, True
is printed otherwise False
.
@*ARGS.classify({ $_ }, :into(my %c)); say ([gcd] %c.values) >= 2
Perl doesn't have a standard gcd()
function so we provide our own. This is a recursive function that takes two
values and provides the greatest common denominator.
sub gcd($a, $b) {
if ($b == 0) {
return $a;
}
gcd($b, $a % $b);
}
We also don't have .classify()
so we emulate it by manually populating a hash with the frequency of individual
elements in @ints
.
my %c;
for my $int (@ints) {
$c{$int}++;
}
Because our gcd()
function only takes two values at a time, we need another loop to get the final GCD.
my $gcd = 0;
for my $v (values %c) {
$gcd = gcd($gcd, $v);
}
If it's greater than or equal to two, we print true
else false
.
say $gcd >= 2 ? 'true' : 'false';
Challenge 2:
Final Score
You are given an array of scores by a team.
Write a script to find the total score of the given team. The score can be any integer,
+
,C
orD
. The+
adds the sum of previous two scores. The scoreC
invalidates the previous score. The scoreD
will double the previous score.
Example 1
Input: @scores = ("5","2","C","D","+")
Output: 30
Round 1: 5
Round 2: 5 + 2
Round 3: 5 (invalidate the previous score 2)
Round 4: 5 + 10 (double the previous score 5)
Round 5: 5 + 10 + 15 (sum of previous two scores)
Total Scores: 30
Example 2
Input: @scores = ("5","-2","4","C","D","9","+","+")
Output: 27
Round 1: 5
Round 2: 5 + (-2)
Round 3: 5 + (-2) + 4
Round 4: 5 + (-2) (invalidate the previous score 4)
Round 5: 5 + (-2) + (-4) (double the previous score -2)
Round 6: 5 + (-2) + (-4) + 9
Round 7: 5 + (-2) + (-4) + 9 + 5 (sum of previous two scores)
Round 8: 5 + (-2) + (-4) + 9 + 5 + 14 (sum of previous two scores)
Total Scores: 27
Example 3
Input: @scores = ("7","D","D","C","+","3")
Output: 45
Round 1: 7
Round 2: 7 + 14 (double the previous score 7)
Round 3: 7 + 14 + 28 (double the previous score 14)
Round 4: 7 + 14 (invalidate the previous score 28)
Round 5: 7 + 14 + 21 (sum of previous two scores)
Round 6: 7 + 14 + 21 + 3
Total Scores: 45
Example 4
Input: @scores = ("-5","-10","+","D","C","+")
Output: -55
Round 1: (-5)
Round 2: (-5) + (-10)
Round 3: (-5) + (-10) + (-15) (sum of previous two scores)
Round 4: (-5) + (-10) + (-15) + (-30) (double the previous score -15)
Round 5: (-5) + (-10) + (-15) (invalidate the previous score -30)
Round 6: (-5) + (-10) + (-15) + (-25) (sum of previous two scores)
Total Scores: -55
Example 5
Input: @scores = ("3","6","+","D","C","8","+","D","-2","C","+")
Output: 128
Round 1: 3
Round 2: 3 + 6
Round 3: 3 + 6 + 9 (sum of previous two scores)
Round 4: 3 + 6 + 9 + 18 (double the previous score 9)
Round 5: 3 + 6 + 9 (invalidate the previous score 18)
Round 6: 3 + 6 + 9 + 8
Round 7: 3 + 6 + 9 + 8 + 17 (sum of previous two scores)
Round 8: 3 + 6 + 9 + 8 + 17 + 34 (double the previous score 17)
Round 9: 3 + 6 + 9 + 8 + 17 + 34 + (-2)
Round 10: 3 + 6 + 9 + 8 + 17 + 34 (invalidate the previous score -2)
Round 11: 3 + 6 + 9 + 8 + 17 + 34 + 51 (sum of previous two scores)
Total Scores: 128
This solution is longer than one line but quite straightforward. In essence we are just writing down the spec in code.
We will be adding and potentially removing scores from the list we will finally work with and the best data structure
for that kind of thing is a stack. We use a Perl Array
for this.
my @previous;
For each score...
for @scores -> $score {
given $score {
...it being a number will be the most common situation. In this case we just add it to the stack.
default {
@previous.push($score);
}
Otherwise if it is one of the three special characters, we just perform whatever operations the spec described and adjust the stack accordingly.
when 'C' {
@previous.pop;
}
when 'D' {
@previous.push(2 * @previous[*-1]);
}
when '+' {
@previous.push(@previous[*-1] + @previous[*-2]);
}
}
}
Finally we take the .sum()
of the stack and print it out.
say @previous.sum;
For the Perl version, we have to provide our own sum()
function. While Perl does have given/when
they have been
deprecated and will be removed by the time I next upgrade my version of Perl. So instead I used a series of if/else
s.
my @previous;
for my $score (@scores) {
if ($score eq 'C') {
pop @previous;
}
elsif ($score eq 'D') {
push @previous, 2 * $previous[-1];
}
elsif ($score eq '+') {
push @previous, $previous[-1] + $previous[-2];
}
else {
push @previous, $score;
}
}
say sum(@previous);