diff options
| author | PerlMonk-Athanasius <PerlMonk.Athanasius@gmail.com> | 2025-08-28 23:44:24 +1000 |
|---|---|---|
| committer | PerlMonk-Athanasius <PerlMonk.Athanasius@gmail.com> | 2025-08-28 23:44:24 +1000 |
| commit | b44326538e5cc10135aeea3d146e7aac2747cd65 (patch) | |
| tree | 7feed5988547f7ae4a593ea1d9e939a8115610d2 | |
| parent | 879e6779474591e9050c4093847eb45a8fae96e0 (diff) | |
| download | perlweeklychallenge-club-b44326538e5cc10135aeea3d146e7aac2747cd65.tar.gz perlweeklychallenge-club-b44326538e5cc10135aeea3d146e7aac2747cd65.tar.bz2 perlweeklychallenge-club-b44326538e5cc10135aeea3d146e7aac2747cd65.zip | |
Perl & Raku solutions to Tasks 1 & 2 for Week 336
| -rw-r--r-- | challenge-336/athanasius/perl/ch-1.pl | 249 | ||||
| -rw-r--r-- | challenge-336/athanasius/perl/ch-2.pl | 256 | ||||
| -rw-r--r-- | challenge-336/athanasius/raku/ch-1.raku | 280 | ||||
| -rw-r--r-- | challenge-336/athanasius/raku/ch-2.raku | 252 |
4 files changed, 1037 insertions, 0 deletions
diff --git a/challenge-336/athanasius/perl/ch-1.pl b/challenge-336/athanasius/perl/ch-1.pl new file mode 100644 index 0000000000..de49ab58bc --- /dev/null +++ b/challenge-336/athanasius/perl/ch-1.pl @@ -0,0 +1,249 @@ +#!perl + +################################################################################ +=comment + +Perl Weekly Challenge 336 +========================= + +TASK #1 +------- +*Equal Group* + +Submitted by: Mohammad Sajid Anwar + +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) + +=cut +################################################################################ + +#--------------------------------------# +# Copyright © 2025 PerlMonk Athanasius # +#--------------------------------------# + +#=============================================================================== +=comment + +Interface +--------- +1. If no command-line arguments are given, the test suite is run. Otherwise: +2. A non-empty list of integers is entered on the command-line. + +Algorithm +--------- +The integers in the input list are merely markers -- they could just as well be +characters or words. The order in which they appear is also irrelevant. What +matters is the frequency with which they appear in the list: + +(1) If any list element occurs only once, no solution is possible. + +(2) Otherwise, a solution is possible if and only if the frequencies share a + common divisor greater than 1. This gives rise to the following algorithm: + + 1. For each element in the list, find its frequency, f. + 2. For each f, find the bag (multiset) containing the prime factors of f. + 3. Find X, the intersection of all bags found in step 2. + 4. If X is empty, no solution is possible. + 5. Otherwise, find p, the product of all the members of X. + 6. A solution may now be generated by partitioning the elements of the + original list into same-element bags, each of size p. + +=cut +#=============================================================================== + +use v5.38.2; # Enables strictures +use warnings; +use Const::Fast; +use Math::Prime::Util qw( factor ); +use Regexp::Common qw( number ); +use Set::Bag; +use Test::More; + +const my $USAGE => <<END; +Usage: + perl $0 [<ints> ...] + perl $0 + + [<ints> ...] A non-empty list of integers +END +#------------------------------------------------------------------------------- +BEGIN +#------------------------------------------------------------------------------- +{ + $| = 1; + print "\nChallenge 336, Task #1: Equal Group (Perl)\n\n"; +} + +#=============================================================================== +MAIN: +#=============================================================================== +{ + if (scalar @ARGV == 0) + { + run_tests(); + } + else + { + my @ints = @ARGV; + + for (@ints) + { + / ^ $RE{num}{int} $ /x or error( qq["$_" is not a valid integer] ); + } + + printf "Input: \@ints = (%s)\n", join ',', @ints; + + my ($result, $groups) = equal_groups( \@ints ); + + printf "Output: %s\n", $result ? 'true' : 'false'; + + if ($result) + { + printf "\nGroups: %s\n", + join ', ', map { '(' . join( ',', @$_ ) . ')' } @$groups; + } + } +} + +#------------------------------------------------------------------------------- +sub equal_groups +#------------------------------------------------------------------------------- +{ + my ($ints) = @_; + my $result = ''; + my $groups = []; + my %count; + ++$count{ $_ } for @$ints; + + my @counts = sort { $a <=> $b } values %count; + + if ($counts[ 0 ] > 1) # If the smallest count is 1, no solution is possible + { + my $common_prime_factors = Set::Bag->new; + $common_prime_factors->insert( $_ => 1 ) for factor( $counts[ 0 ] ); + + for my $i (1 .. $#counts) + { + my $prime_factors = Set::Bag->new; + $prime_factors ->insert( $_ => 1 ) for factor( $counts[ $i ] ); + + $common_prime_factors &= $prime_factors; # Keep the intersection + } + + my @factors = $common_prime_factors->elements; + + if (scalar @factors > 0) + { + $result = 1; + $groups = make_groups( \%count, \@factors ); + } + } + + return ($result, $groups); +} + +#------------------------------------------------------------------------------- +sub make_groups +#------------------------------------------------------------------------------- +{ + my ($count, $factors) = @_; + my @groups; + my $product = 1; + $product *= $_ for @$factors; + + for my $n (sort { $a <=> $b } keys %$count) + { + my $multiplier = $count->{ $n } / $product; + + push @groups, [ ($n) x $product ] for 1 .. $multiplier; + } + + return \@groups; +} + +#------------------------------------------------------------------------------- +sub run_tests +#------------------------------------------------------------------------------- +{ + print "Running the test suite\n"; + + while (my $line = <DATA>) + { + chomp $line; + + my ($test_name, $ints_str, $expected_str, $groups_str) = + split / \| /x, $line; + + for ($test_name, $ints_str, $expected_str, $groups_str) + { + s/ ^ \s+ //x; + s/ \s+ $ //x; + } + + my @ints = split / \s+ /x, $ints_str; + my ($result, $groups) = equal_groups( \@ints ); + my $expected = $expected_str eq 'true'; + my @group_str = split / \; \s* /x, $groups_str; + my @exp_groups = map { [ split / \s+ /x, $_ ] } @group_str; + + is $result, $expected, "$test_name: Result"; + is_deeply $groups, \@exp_groups, "$test_name: Groups" if $result; + } + + done_testing; +} + +#------------------------------------------------------------------------------- +sub error +#------------------------------------------------------------------------------- +{ + my ($message) = @_; + + die "ERROR: $message\n$USAGE"; +} + +################################################################################ + +__DATA__ +Example 1|1 1 2 2 2 2 |true |1 1; 2 2; 2 2 +Example 2|1 1 1 2 2 2 3 3 |false|1 1 1; 2 2 2; 3 3 +Example 3|5 5 5 5 5 5 7 7 7 7 7 7|true |5 5 5 5 5 5; 7 7 7 7 7 7 +Example 4|1 2 3 4 |false| +Example 5|8 8 9 9 10 10 11 11 |true |8 8; 9 9; 10 10; 11 11 diff --git a/challenge-336/athanasius/perl/ch-2.pl b/challenge-336/athanasius/perl/ch-2.pl new file mode 100644 index 0000000000..a834ebfead --- /dev/null +++ b/challenge-336/athanasius/perl/ch-2.pl @@ -0,0 +1,256 @@ +#!perl + +################################################################################ +=comment + +Perl Weekly Challenge 336 +========================= + +TASK #2 +------- +*Final Score* + +Submitted by: Mohammad Sajid Anwar + +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 or D. The + adds the sum of previous two scores. The score C +invalidates the previous score. The score D 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 + +=cut +################################################################################ + +#--------------------------------------# +# Copyright © 2025 PerlMonk Athanasius # +#--------------------------------------# + +#=============================================================================== +=comment + +Interface +--------- +1. If no command-line arguments are given, the test suite is run. Otherwise: +2. A non-empty list of scores is entered on the command-line. + +Assumptions +----------- +During score evaluation, it is an error if a "+" score is not preceded by at +least two other (integer) scores. Likewise, it is an error if a "C" or a "D" +score is not preceded by at least one other (integer) score. + +=cut +#=============================================================================== + +use v5.38.2; # Enables strictures +use warnings; +use Const::Fast; +use List::Util qw( sum0 ); +use Regexp::Common qw( number ); +use Test::More; +use Try::Tiny; + +const my $USAGE => <<END; +Usage: + perl $0 [<scores> ...] + perl $0 + + [<scores> ...] A non-empty list of scores (integers, '+', 'C', 'D') +END + +#------------------------------------------------------------------------------- +BEGIN +#------------------------------------------------------------------------------- +{ + $| = 1; + print "\nChallenge 336, Task #2: Final Score (Perl)\n\n"; +} + +#=============================================================================== +MAIN: +#=============================================================================== +{ + if (scalar @ARGV == 0) + { + run_tests(); + } + else + { + my @scores = @ARGV; + + for (@scores) + { + / ^ $RE{num}{int} | [+CD] $ /x + or error( qq["$_" is not a valid score] ); + } + + printf "Input: \@scores = (%s)\n", join ',', map { qq["$_"] } @scores; + + try + { + my $score = find_final_score( \@scores ); + + print "Output: $score\n"; + } + catch + { + print "\nERROR: $_"; + }; + } +} + +#------------------------------------------------------------------------------- +sub find_final_score +#------------------------------------------------------------------------------- +{ + my ($scores) = @_; + my @queue; + + for my $score (@$scores) + { + my $q_len = scalar @queue; + + if ($score eq '+') + { + die qq[Illegal "+" score placement\n] if $q_len < 2; + push @queue, $queue[ -2 ] + $queue[ -1 ]; + } + elsif ($score eq 'C') + { + die qq[Illegal "C" score placement\n] if $q_len < 1; + pop @queue; + } + elsif ($score eq 'D') + { + die qq[Illegal "D" score placement\n] if $q_len < 1; + push @queue, 2 * $queue[ -1 ]; + } + else # $score must be an integer + { + push @queue, $score; + } + } + + return sum0 @queue; +} + +#------------------------------------------------------------------------------- +sub run_tests +#------------------------------------------------------------------------------- +{ + print "Running the test suite\n"; + + while (my $line = <DATA>) + { + chomp $line; + + my ($test_name, $scores_str, $expected) = split / \| /x, $line; + + for ($test_name, $scores_str, $expected) + { + s/ ^ \s+ //x; + s/ \s+ $ //x; + } + + my @scores = split / \s+ /x, $scores_str; + my $score = find_final_score( \@scores ); + + is $score, $expected, $test_name; + } + + done_testing; +} + +#------------------------------------------------------------------------------- +sub error +#------------------------------------------------------------------------------- +{ + my ($message) = @_; + + die "ERROR: $message\n$USAGE"; +} + +################################################################################ + +__DATA__ +Example 1| 5 2 C D + | 30 +Example 2| 5 -2 4 C D 9 + + | 27 +Example 3| 7 D D C + 3 | 45 +Example 4|-5 -10 + D C + |-55 +Example 5| 3 6 + D C 8 + D -2 C +|128 diff --git a/challenge-336/athanasius/raku/ch-1.raku b/challenge-336/athanasius/raku/ch-1.raku new file mode 100644 index 0000000000..7d2b43affe --- /dev/null +++ b/challenge-336/athanasius/raku/ch-1.raku @@ -0,0 +1,280 @@ +use v6d; + +################################################################################ +=begin comment + +Perl Weekly Challenge 336 +========================= + +TASK #1 +------- +*Equal Group* + +Submitted by: Mohammad Sajid Anwar + +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) + +=end comment +################################################################################ + +#--------------------------------------# +# Copyright © 2025 PerlMonk Athanasius # +#--------------------------------------# + +#=============================================================================== +=begin comment + +Interface +--------- +1. If no command-line arguments are given, the test suite is run. Otherwise: +2. A non-empty list of integers is entered on the command-line. +3. If the first integer is negative, it must be preceded by "--" to indicate + that it is not a command-line flag. + +Algorithm +--------- +The integers in the input list are merely markers -- they could just as well be +characters or words. The order in which they appear is also irrelevant. What +matters is the frequency with which they appear in the list: + +(1) If any list element occurs only once, no solution is possible. + +(2) Otherwise, a solution is possible if and only if the frequencies share a + common divisor greater than 1. This gives rise to the following algorithm: + + 1. For each element in the list, find its frequency, f. + 2. For each f, find the bag (multiset) containing the prime factors of f. + 3. Find X, the intersection of all bags found in step 2. + 4. If X is empty, no solution is possible. + 5. Otherwise, find p, the product of all the members of X. + 6. A solution may now be generated by partitioning the elements of the + original list into same-element bags, each of size p. + +=end comment +#=============================================================================== + +use Test; + +subset Result of List where (Bool, Array[Array[Int]]); + +#------------------------------------------------------------------------------- +BEGIN +#------------------------------------------------------------------------------- +{ + "\nChallenge 336, Task #1: Equal Group (Raku)\n".put; +} + +#=============================================================================== +multi sub MAIN +( + #| A non-empty list of integers + + *@ints where { .elems > 0 && .all ~~ Int:D } +) +#=============================================================================== +{ + "Input: \@ints = (%s)\n".printf: @ints.join: ','; + + my Result $result = equal-groups( @ints ); + + "Output: %s\n".printf: $result[ 0 ] ?? 'true' !! 'false'; + + if $result[ 0 ] + { + "\nGroups: %s\n".printf: + $result[ 1 ].map( { '(' ~ .join( ',' ) ~ ')' } ).join: ', '; + } +} + +#=============================================================================== +multi sub MAIN() # No input: run the test suite +#=============================================================================== +{ + run-tests(); +} + +#------------------------------------------------------------------------------- +sub equal-groups( List:D[Int:D] $ints --> Result:D ) +#------------------------------------------------------------------------------- +{ + my Array[Int] @groups; + my Bool $result = False; + my UInt %count{Int}; + ++%count{ +$_ } for @$ints; + my UInt @counts = %count.values.sort; + + if @counts[ 0 ] > 1 # If the smallest count is 1, no solution is possible + { + my BagHash[Int] $common-factors = BagHash[Int].new; + $common-factors.add: $_ for factor( @counts[ 0 ] ); + + for 1 .. @counts.end -> UInt $i + { + my BagHash[Int] $factors = BagHash[Int].new; + $factors.add: $_ for factor( @counts[ $i ] ); + + # Keep only the intersection + $common-factors = BagHash[Int].new-from-pairs: + ($common-factors ∩ $factors).list; + } + + my UInt @factors = $common-factors.kxxv.sort; + + if @factors.elems > 0 + { + $result = True; + @groups = make-groups( %count, @factors ); + } + } + + return $result, @groups; +} + +#------------------------------------------------------------------------------- +sub make-groups +( + Hash:D $count, + List:D[UInt:D] $factors +--> Array:D[Array:D[Int:D]] +) +#------------------------------------------------------------------------------- +{ + my Array[Int] @groups; + + my UInt $product = 1; + $product *= $_ for @$factors; + + for $count.keys.sort -> Int $n + { + my UInt $multiplier = $count{ $n } div $product; + + push @groups, Array[Int].new: $n xx $product for 1 .. $multiplier; + } + + return @groups; +} + +#------------------------------------------------------------------------------- +sub factor( UInt:D $int --> List:D[UInt:D] ) +#------------------------------------------------------------------------------- +{ + # Code adapted from "Finding prime factors using Raku" by Andrew Shitov, + # https://andrewshitov.com/2019/09/09/finding-prime-factors-using-perl-6/ + + my Rat $n = $int.Rat; + my UInt @primes = grep { .is-prime }, 1 .. *; + my UInt $pos = 0; + my UInt @factors; + + while $n > 1 + { + my UInt $factor = @primes[ $pos++ ]; + + next unless $n %% $factor; + + $pos = 0; + $n /= $factor; + + @factors.push: $factor; + } + + return @factors; +} + +#------------------------------------------------------------------------------- +sub run-tests() +#------------------------------------------------------------------------------- +{ + 'Running the test suite'.put; + + for test-data.lines -> Str $line + { + my Str ($test-name, $ints-str, $expected-str, $groups-str) = + $line.split: / \| /; + + for $test-name, $ints-str, $expected-str, $groups-str + { + s/ ^ \s+ //; + s/ \s+ $ //; + } + + my Int @ints = $ints-str.split( / \s+ /, :skip-empty ) + .map: { .Int }; + my Result $result = equal-groups( @ints ); + my Bool $expected = $expected-str eq 'true'; + my Str @group-str = $groups-str.split: / \; \s* /, :skip-empty; + my Array[Int] @groups = @group-str.map: + { + Array[Int].new: + .split( / \s+ /, :skip-empty ).map: { .Int } + }; + + is $result[ 0 ], $expected, "$test-name: Result"; + is-deeply $result[ 1 ], @groups, "$test-name: Groups" if $result[ 0 ]; + } + + done-testing; +} + +#------------------------------------------------------------------------------- +sub USAGE() +#------------------------------------------------------------------------------- +{ + my Str $usage = $*USAGE; + + $usage ~~ s:g/ ($*PROGRAM-NAME) /raku $0/; + + $usage.put; +} + +#------------------------------------------------------------------------------- +sub test-data( --> Str:D ) +#------------------------------------------------------------------------------- +{ + return q:to/END/; + Example 1|1 1 2 2 2 2 |true |1 1; 2 2; 2 2 + Example 2|1 1 1 2 2 2 3 3 |false|1 1 1; 2 2 2; 3 3 + Example 3|5 5 5 5 5 5 7 7 7 7 7 7|true |5 5 5 5 5 5; 7 7 7 7 7 7 + Example 4|1 2 3 4 |false| + Example 5|8 8 9 9 10 10 11 11 |true |8 8; 9 9; 10 10; 11 11 + END +} + +################################################################################ diff --git a/challenge-336/athanasius/raku/ch-2.raku b/challenge-336/athanasius/raku/ch-2.raku new file mode 100644 index 0000000000..8efdacd2f8 --- /dev/null +++ b/challenge-336/athanasius/raku/ch-2.raku @@ -0,0 +1,252 @@ +use v6d; + +################################################################################ +=begin comment + +Perl Weekly Challenge 336 +========================= + +TASK #2 +------- +*Final Score* + +Submitted by: Mohammad Sajid Anwar + +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 or D. The + adds the sum of previous two scores. The score C +invalidates the previous score. The score D 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 + +=end comment +################################################################################ + +#--------------------------------------# +# Copyright © 2025 PerlMonk Athanasius # +#--------------------------------------# + +#=============================================================================== +=begin comment + +Interface +--------- +1. If no command-line arguments are given, the test suite is run. Otherwise: +2. A non-empty list of scores is entered on the command-line. +3. If the first score is a negative integer, it must be preceded by "--" to + signal that it is not a command-line flag. + +Assumptions +----------- +During score evaluation, it is an error if a "+" score is not preceded by at +least two other (integer) scores. Likewise, it is an error if a "C" or a "D" +score is not preceded by at least one other (integer) score. + +=end comment +#=============================================================================== + +use Test; + +subset Score where * ~~ Int:D | '+' | 'C' | 'D'; + +#------------------------------------------------------------------------------- +BEGIN +#------------------------------------------------------------------------------- +{ + "\nChallenge 336, Task #2: Final Score (Raku)\n".put; +} + +#=============================================================================== +multi sub MAIN +( + #| A non-empty list of scores (integers, '+', 'C', 'D') + + *@scores where { .elems > 0 && .all ~~ Score:D } +) +#=============================================================================== +{ + CATCH + { + when X::AdHoc + { + "\nERROR: { .message }".put; + exit 0; + } + } + + "Input: @scores = (%s)\n".printf: @scores.map( { qq["$_"] } ).join: ','; + + my Int $score = find-final-score( @scores ); + + "Output: $score".put; +} + +#=============================================================================== +multi sub MAIN() # No input: run the test suite +#=============================================================================== +{ + run-tests(); +} + +#------------------------------------------------------------------------------- +sub find-final-score( List:D[Score:D] $scores --> Int:D ) +#------------------------------------------------------------------------------- +{ + my Score @queue; + + for @$scores -> Score $score + { + my UInt $q-len = @queue.elems; + + given $score + { + when '+' { + die qq[Illegal "+" score placement] if 2 > $q-len; + @queue.push: [+] @queue[ *-2 .. *-1 ]; + } + + when 'C' { + die qq[Illegal "C" score placement] if 1 > $q-len; + @queue.pop; + } + + when 'D' { + die qq[Illegal "D" score placement] if 1 > $q-len; + @queue.push: 2 * @queue[ *-1 ]; + } + + default { # $score must be an integer + @queue.push: $score; + } + } + } + + return [+] @queue; +} + +#------------------------------------------------------------------------------- +sub run-tests() +#------------------------------------------------------------------------------- +{ + 'Running the test suite'.put; + + for test-data.lines -> Str $line + { + my Str ($test-name, $scores-str, $expected) = $line.split: / \| /; + + for $test-name, $scores-str, $expected + { + s/ ^ \s+ //; + s/ \s+ $ //; + } + + my Str @strings = $scores-str.split: / \s+ /, :skip-empty; + my Score @scores = @strings.map: { / ^ <[+CD]> $ / ?? $_ !! .Int }; + my Int $score = find-final-score( @scores ); + + is $score, $expected.Int, $test-name; + } + + done-testing; +} + +#------------------------------------------------------------------------------- +sub USAGE() +#------------------------------------------------------------------------------- +{ + my Str $usage = $*USAGE; + + $usage ~~ s:g/ ($*PROGRAM-NAME) /raku $0/; + + $usage.put; +} + +#------------------------------------------------------------------------------- +sub test-data( --> Str:D ) +#------------------------------------------------------------------------------- +{ + return q:to/END/; + Example 1| 5 2 C D + | 30 + Example 2| 5 -2 4 C D 9 + + | 27 + Example 3| 7 D D C + 3 | 45 + Example 4|-5 -10 + D C + |-55 + Example 5| 3 6 + D C 8 + D -2 C +|128 + END +} + +################################################################################ |
