diff options
| author | PerlMonk-Athanasius <PerlMonk.Athanasius@gmail.com> | 2023-07-30 23:05:55 +1000 |
|---|---|---|
| committer | PerlMonk-Athanasius <PerlMonk.Athanasius@gmail.com> | 2023-07-30 23:05:55 +1000 |
| commit | 6ee04682659df40ba2c1eb09d1fa36245ed8b5d7 (patch) | |
| tree | 657dbe33ea31c948c965fd7d87c9ae3211876985 /challenge-227 | |
| parent | fe6b182a3e03e5219bb444d3924536d0ec8471ea (diff) | |
| download | perlweeklychallenge-club-6ee04682659df40ba2c1eb09d1fa36245ed8b5d7.tar.gz perlweeklychallenge-club-6ee04682659df40ba2c1eb09d1fa36245ed8b5d7.tar.bz2 perlweeklychallenge-club-6ee04682659df40ba2c1eb09d1fa36245ed8b5d7.zip | |
Perl & Raku solutions to Tasks 1 & 2 for Week 227
Diffstat (limited to 'challenge-227')
| -rw-r--r-- | challenge-227/athanasius/perl/ch-1.pl | 180 | ||||
| -rw-r--r-- | challenge-227/athanasius/perl/ch-2.pl | 262 | ||||
| -rw-r--r-- | challenge-227/athanasius/raku/ch-1.raku | 176 | ||||
| -rw-r--r-- | challenge-227/athanasius/raku/ch-2.raku | 271 |
4 files changed, 889 insertions, 0 deletions
diff --git a/challenge-227/athanasius/perl/ch-1.pl b/challenge-227/athanasius/perl/ch-1.pl new file mode 100644 index 0000000000..b11a838279 --- /dev/null +++ b/challenge-227/athanasius/perl/ch-1.pl @@ -0,0 +1,180 @@ +#!perl + +################################################################################ +=comment + +Perl Weekly Challenge 227 +========================= + +TASK #1 +------- +*Friday 13th* + +Submitted by: Peter Campbell Smith + +You are given a year number in the range 1753 to 9999. + +Write a script to find out how many dates in the year are Friday 13th, assume +that the current Gregorian calendar applies. + +Example + + Input: $year = 2023 + Output: 2 + + Since there are only 2 Friday 13th in the given year 2023 i.e. 13th Jan and + 13th Oct. + +=cut +################################################################################ + +#--------------------------------------# +# Copyright © 2023 PerlMonk Athanasius # +#--------------------------------------# + +#=============================================================================== +=comment + +Interface +--------- +1. If no command-line arguments are given, the test suite is run. Otherwise: +2. If $VERBOSE is set to a true value, the solution (the number of dates) is + followed by a list of the months in the given year in which the 13th is a + Friday. + +Note +---- +"Friday the 13th .... occurs when the 13th day of the month in the Gregorian +calendar falls on a Friday, which happens at least once every year but can occur +up to three times in the same year." --"Friday the 13th", Wikipedia + +=cut +#=============================================================================== + +use strict; +use warnings; +use Const::Fast; +use DateTime; +use Regexp::Common qw( number ); +use Test::More; + +const my $FRIDAY => 5; # DateTime::day_of_week() outputs 1-7, Monday is 1 +const my $MIN => 1753; # First valid year +const my $MAX => 9999; # Last valid year +const my $VERBOSE => 1; # Output month names in addition to the count +const my $USAGE => +"Usage: + perl $0 <year> + perl $0 + + <year> A year in the range $MIN to $MAX\n"; + +#------------------------------------------------------------------------------- +BEGIN +#------------------------------------------------------------------------------- +{ + $| = 1; + print "\nChallenge 227, Task #1: Friday 13th (Perl)\n\n"; +} + +#=============================================================================== +MAIN: +#=============================================================================== +{ + my $args = scalar @ARGV; + + if ($args == 0) + { + run_tests(); + } + elsif ($args == 1) + { + my $year = $ARGV[ 0 ]; + + $year =~ / ^ $RE{num}{int} $ /x + or error( qq["$year" is not a valid integer] ); + $MIN <= $year <= $MAX or error( qq["$year" is out of range] ); + + print "Input: \$year = $year\n"; + + my $list = find_friday_13th( $year ); + my $count = scalar @$list; + + print "Output: $count\n"; + + if ($VERBOSE) + { + printf "\nMonth%s in %d in which 13th falls on a Friday: %s\n", + $count == 1 ? '' : 's', $year, join ', ', @$list; + } + } + else + { + error( "Expected 1 or 0 command-line arguments, found $args" ); + } +} + +#------------------------------------------------------------------------------- +sub find_friday_13th +#------------------------------------------------------------------------------- +{ + my ($year) = @_; + my @list; + + for my $month (1 .. 12) + { + my $dt = DateTime->new( year => $year, month => $month, day => 13 ); + my $dow = $dt->day_of_week; + + push @list, $dt->month_abbr if $dow == $FRIDAY; + } + + return \@list; +} + +#------------------------------------------------------------------------------- +sub run_tests +#------------------------------------------------------------------------------- +{ + print "Running the test suite\n"; + + while (my $line = <DATA>) + { + chomp $line; + + my ($test, $year, $exp_count, $exp_list_str) = split / \| /x, $line; + + for ($test, $year, $exp_count, $exp_list_str) + { + s/ ^ \s+ //x; + s/ \s+ $ //x; + } + + my @exp_list = split / \s+ /x, $exp_list_str; + my $list = find_friday_13th( $year ); + my $count = scalar @$list; + + is $count, $exp_count, "$test: count"; + is_deeply $list, \@exp_list, "$test: months"; + } + + done_testing; +} + +#------------------------------------------------------------------------------- +sub error +#------------------------------------------------------------------------------- +{ + my ($message) = @_; + + die "ERROR: $message\n$USAGE"; +} + +################################################################################ + +__DATA__ +Example |2023|2|Jan Oct +Wikipedia 1|2015|3|Feb Mar Nov +Wikipedia 2|1960|1|May +Wikipedia 3|1900|2|Apr Jul +Wikipedia 4|2099|3|Feb Mar Nov diff --git a/challenge-227/athanasius/perl/ch-2.pl b/challenge-227/athanasius/perl/ch-2.pl new file mode 100644 index 0000000000..74ad9bbf95 --- /dev/null +++ b/challenge-227/athanasius/perl/ch-2.pl @@ -0,0 +1,262 @@ +#!perl + +################################################################################ +=comment + +Perl Weekly Challenge 227 +========================= + +TASK #2 +------- +*Roman Maths* + +Submitted by: Peter Campbell Smith + +Write a script to handle a 2-term arithmetic operation expressed in Roman +numeral. + +Example + + IV + V => IX + M - I => CMXCIX + X / II => V + XI * VI => LXVI + VII ** III => CCCXLIII + V - V => nulla (they knew about zero but didn't have a symbol) + V / II => non potest (they didn't do fractions) + MMM + M => non potest (they only went up to 3999) + V - X => non potest (they didn't do negative numbers) + +=cut +################################################################################ + +#--------------------------------------# +# Copyright © 2023 PerlMonk Athanasius # +#--------------------------------------# + +#=============================================================================== +=comment + +Interface +--------- +If no command-line arguments are given, the test suite is run. + +Notes +----- +1. Roman numerals in lower case will be converted to upper case. +2. A non-standard form such as "IM" is (incorrectly?) interpreted as 1001, not + 999 as might be expected. + +=cut +#=============================================================================== + +use strict; +use warnings; +use Const::Fast; +use Switch::Plain; +use Test::More; +use Text::Roman qw( int2roman isroman roman2int ); + +const my $MAX_ROM_NUM => 3999; +const my @OPERATORS => qw( + - * / ** ); +const my $ROM_NUM_ZERO => 'nulla'; # Latin "no", Italian "nothing" +const my $UNDEFINED => 'non potest'; # Latin "can not" +const my $USAGE => +"Usage: + perl $0 <operand1> <operator> <operand2> + perl $0 + + <operand1> Left operand: number in Roman numerals + <operator> Arithmetic operator: + - * / ** + <operand2> Right operand: number in Roman numerals\n"; + +#------------------------------------------------------------------------------- +BEGIN +#------------------------------------------------------------------------------- +{ + $| = 1; + print "\nChallenge 227, Task #2: Roman Maths (Perl)\n\n"; +} + +#=============================================================================== +MAIN: +#=============================================================================== +{ + my $args = scalar @ARGV; + + if ($args == 0) + { + run_tests(); + } + elsif ($args == 3) + { + my ($operand1, $operator, $operand2) = parse_command_line(); + + print "Input: $operand1 $operator $operand2\n"; + + my $result = calculate( $operand1, $operator, $operand2 ); + + print "Output: $result\n"; + } + else + { + error( "Expected 0 or 3 command-line arguments, found $args" ); + } +} + +#------------------------------------------------------------------------------- +sub calculate +#------------------------------------------------------------------------------- +{ + my ($operand1, $operator, $operand2) = @_; + my $result; + my $left_op = roman2int( $operand1 ); + my $right_op = roman2int( $operand2 ); + + sswitch ($operator) + { + case '+': { $result = add( $left_op, $right_op ); } + case '-': { $result = subtract( $left_op, $right_op ); } + case '*': { $result = multiply( $left_op, $right_op ); } + case '/': { $result = divide( $left_op, $right_op ); } + case '**': { $result = raise_to_power( $left_op, $right_op ); } + default: { die qq[Invalid operator "$operator"]; } + } + + return $result; +} + +#------------------------------------------------------------------------------- +sub add +#------------------------------------------------------------------------------- +{ + my ($left_op, $right_op) = @_; + + my $sum = $left_op + $right_op; + + return $sum > $MAX_ROM_NUM ? $UNDEFINED : int2roman( $sum ); +} + +#------------------------------------------------------------------------------- +sub subtract +#------------------------------------------------------------------------------- +{ + my ($left_op, $right_op) = @_; + + my $difference = $left_op - $right_op; + + return $difference == 0 ? $ROM_NUM_ZERO : + $difference < 0 ? $UNDEFINED : int2roman( $difference ); +} + +#------------------------------------------------------------------------------- +sub multiply +#------------------------------------------------------------------------------- +{ + my ($left_op, $right_op) = @_; + + my $product = $left_op * $right_op; + + return $product > $MAX_ROM_NUM ? $UNDEFINED : int2roman( $product ); +} + +#------------------------------------------------------------------------------- +sub divide +#------------------------------------------------------------------------------- +{ + my ($left_op, $right_op) = @_; + + return ($left_op % $right_op) ? $UNDEFINED : + int2roman( int( $left_op / $right_op ) ); +} + +#------------------------------------------------------------------------------- +sub raise_to_power +#------------------------------------------------------------------------------- +{ + my ($left_op, $right_op) = @_; + + my $power = $left_op ** $right_op; + + return $power > $MAX_ROM_NUM ? $UNDEFINED : int2roman( $power ); +} + +#------------------------------------------------------------------------------- +sub parse_command_line +#------------------------------------------------------------------------------- +{ + my ($operand1, $operator, $operand2) = @ARGV; + + $operand1 = uc $operand1; + $operand2 = uc $operand2; + + isroman( $operand1 ) + or error( qq["$operand1" is not a valid Roman numeral] ); + + my $valid_op = 0; + + for my $op (@OPERATORS) + { + if ($operator eq $op) + { + $valid_op = 1; + last; + } + } + + $valid_op or error( qq["$operator" is not a valid arithmetic operator] ); + + isroman( $operand2 ) + or error( qq["$operand2" is not a valid Roman numeral] ); + + return ($operand1, $operator, $operand2); +} + +#------------------------------------------------------------------------------- +sub run_tests +#------------------------------------------------------------------------------- +{ + print "Running the test suite\n"; + + while (my $line = <DATA>) + { + chomp $line; + + my ($test_name, $op1, $opr, $op2, $expected) = split / \| /x, $line; + + for ($test_name, $op1, $opr, $op2, $expected) + { + s/ ^ \s+ //x; + s/ \s+ $ //x; + } + + my $result = calculate( $op1, $opr, $op2 ); + + is $result, $expected, $test_name; + } + + done_testing; +} + +#------------------------------------------------------------------------------- +sub error +#------------------------------------------------------------------------------- +{ + my ($message) = @_; + + die "ERROR: $message\n$USAGE"; +} + +################################################################################ + +__DATA__ +Example 1 |IV |+ |V |IX +Example 2 |M |- |I |CMXCIX +Example 3 |X |/ |II |V +Example 4 |XI |* |VI |LXVI +Example 5 |VII |**|III|CCCXLIII +Example 6 |V |- |V |nulla +Example 7 |V |/ |II |non potest +Example 8 |MMM |+ |M |non potest +Example 9 |V |- |X |non potest +Large division|MMMCMXCVI|/ |IV |CMXCIX diff --git a/challenge-227/athanasius/raku/ch-1.raku b/challenge-227/athanasius/raku/ch-1.raku new file mode 100644 index 0000000000..4124c630d2 --- /dev/null +++ b/challenge-227/athanasius/raku/ch-1.raku @@ -0,0 +1,176 @@ +use v6d; + +################################################################################ +=begin comment + +Perl Weekly Challenge 227 +========================= + +TASK #1 +------- +*Friday 13th* + +Submitted by: Peter Campbell Smith + +You are given a year number in the range 1753 to 9999. + +Write a script to find out how many dates in the year are Friday 13th, assume +that the current Gregorian calendar applies. + +Example + + Input: $year = 2023 + Output: 2 + + Since there are only 2 Friday 13th in the given year 2023 i.e. 13th Jan and + 13th Oct. + +=end comment +################################################################################ + +#--------------------------------------# +# Copyright © 2023 PerlMonk Athanasius # +#--------------------------------------# + +#=============================================================================== +=begin comment + +Interface +--------- +1. If no command-line arguments are given, the test suite is run. Otherwise: +2. If $VERBOSE is set to True, the solution (the number of dates) is followed by + a list of the months in the given year in which the 13th is a Friday. + +Note +---- +"Friday the 13th .... occurs when the 13th day of the month in the Gregorian +calendar falls on a Friday, which happens at least once every year but can occur +up to three times in the same year." --"Friday the 13th", Wikipedia + +=end comment +#=============================================================================== + +use Date::Names; +use Test; + +my UInt constant $FRIDAY = 5; # Date::day-of-week() outputs 1-7, Monday is 1 +my UInt constant $MIN = 1753; +my UInt constant $MAX = 9999; +my Bool constant $VERBOSE = True; + +#------------------------------------------------------------------------------- +BEGIN +#------------------------------------------------------------------------------- +{ + "\nChallenge 227, Task #1: Friday 13th (Raku)\n".put; +} + +#=============================================================================== +multi sub MAIN +( + UInt:D $year where $MIN <= * <= $MAX #= A year in the range 1753 to 9999 +) +#=============================================================================== +{ + "Input: \$year = $year".put; + + my Str @list = find-friday_13th( $year ); + my UInt $count = @list.elems; + + "Output: $count".put; + + if $VERBOSE + { + "\nMonth%s in %d in which 13th falls on a Friday: %s\n".printf: + $count == 1 ?? '' !! 's', $year, @list.join: ', '; + } +} + +#=============================================================================== +multi sub MAIN() # No input: run the test suite +#=============================================================================== +{ + run-tests(); +} + +#------------------------------------------------------------------------------- +sub find-friday_13th( UInt:D $year where $MIN <= * <= $MAX --> List:D[Str:D] ) +#------------------------------------------------------------------------------- +{ + my Date::Names $dn = Date::Names.new: :lang< en >, :mset< mon3 >; + my Str @list; + + for 1 .. 12 -> UInt $month + { + my Date $date = Date.new: year => $year, month => $month, day => 13; + my UInt $dow = $date.day-of-week; + + @list.push: $dn.mon( $month ) if $dow == $FRIDAY; + } + + return @list; +} + +#------------------------------------------------------------------------------- +sub run-tests() +#------------------------------------------------------------------------------- +{ + 'Running the test suite'.put; + + for test-data.lines -> Str $line + { + my Str ($test, $year, $exp-count, $exp-list-str) = $line.split: / \| /; + + for $test, $year, $exp-count, $exp-list-str + { + s/ ^ \s+ //; + s/ \s+ $ //; + } + + my Str @exp-list = $exp-list-str.split: / \s+ /; + my Str @list = find-friday_13th( $year.Int ); + my UInt $count = @list.elems; + + is $count, $exp-count.Int, "$test: count"; + is-deeply @list, @exp-list, "$test: months"; + } + + done-testing; +} + +#------------------------------------------------------------------------------- +sub error( Str:D $message ) +#------------------------------------------------------------------------------- +{ + "ERROR: $message".put; + + USAGE(); + + exit 0; +} + +#------------------------------------------------------------------------------- +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 |2023|2|Jan Oct + Wikipedia 1|2015|3|Feb Mar Nov + Wikipedia 2|1960|1|May + Wikipedia 3|1900|2|Apr Jul + Wikipedia 4|2099|3|Feb Mar Nov + END +} + +################################################################################ diff --git a/challenge-227/athanasius/raku/ch-2.raku b/challenge-227/athanasius/raku/ch-2.raku new file mode 100644 index 0000000000..a24a6b882f --- /dev/null +++ b/challenge-227/athanasius/raku/ch-2.raku @@ -0,0 +1,271 @@ +use v6d; + +################################################################################ +=begin comment + +Perl Weekly Challenge 227 +========================= + +TASK #2 +------- +*Roman Maths* + +Submitted by: Peter Campbell Smith + +Write a script to handle a 2-term arithmetic operation expressed in Roman +numeral. + +Example + + IV + V => IX + M - I => CMXCIX + X / II => V + XI * VI => LXVI + VII ** III => CCCXLIII + V - V => nulla (they knew about zero but didn't have a symbol) + V / II => non potest (they didn't do fractions) + MMM + M => non potest (they only went up to 3999) + V - X => non potest (they didn't do negative numbers) + +=end comment +################################################################################ + +#--------------------------------------# +# Copyright © 2023 PerlMonk Athanasius # +#--------------------------------------# + +#=============================================================================== +=begin comment + +Interface +--------- +If no command-line arguments are given, the test suite is run. + +Notes +----- +1. Roman numerals in lower case will be converted to upper case. +2. A non-standard form such as "IM" is (incorrectly?) interpreted as 1001, not + 999 as might be expected. + +=end comment +#=============================================================================== + +use Math::Roman; +use Test; + +my UInt constant $MAX-ROM-NUM = 3999; +my constant @OPERATORS = Array[Str].new: < + - * / ** >; +my Str constant $ROM-NUM-ZERO = 'nulla'; # Latin "no", Italian "nothing" +my Str constant $UNDEFINED = 'non potest'; # Latin "can not" + +#------------------------------------------------------------------------------- +BEGIN +#------------------------------------------------------------------------------- +{ + "\nChallenge 227, Task #2: Roman Maths (Raku)\n".put; +} + +#=============================================================================== +multi sub MAIN +( + Str:D $operand1, #= Left operand: number in Roman numerals + Str:D $operator, #= Arithmetic operator: + - * / ** + Str:D $operand2 #= Right operand: number in Roman numerals +) +#=============================================================================== +{ + my Str $op1 = $operand1.uc; + my Str $op2 = $operand2.uc; + + is-roman-num( $op1 ) + or error( qq["$op1" is not a valid Roman numeral] ); + + is-operator\( $operator ) + or error( qq["$operator" is not a valid arithmetic operator] ); + + is-roman-num( $op2 ) + or error( qq["$op2" is not a valid Roman numeral] ); + + "Input: $op1 $operator $op2".put; + + my Str $result = calculate( $op1, $operator, $op2 ); + + "Output: $result".put; +} + +#=============================================================================== +multi sub MAIN() # No input: run the test suite +#=============================================================================== +{ + run-tests(); +} + +#------------------------------------------------------------------------------- +sub calculate( Str:D $operand1, Str:D $operator, Str:D $operand2 --> Str:D ) +#------------------------------------------------------------------------------- +{ + my Str $result; + my Math::Roman $left-op = Math::Roman.new: $operand1; + my Math::Roman $right-op = Math::Roman.new: $operand2; + + given $operator + { + when '+' { $result = add( $left-op, $right-op ); } + when '-' { $result = subtract( $left-op, $right-op ); } + when '*' { $result = multiply( $left-op, $right-op ); } + when '/' { $result = divide( $left-op, $right-op ); } + when '**' { $result = raise-to-power( $left-op, $right-op ); } + default { die qq[Invalid operator "$operator"]; } + } + + return $result; +} + +#------------------------------------------------------------------------------- +sub add( Math::Roman:D $left-op, Math::Roman:D $right-op --> Str:D ) +#------------------------------------------------------------------------------- +{ + my UInt $sum = $left-op + $right-op; + + return $sum > $MAX-ROM-NUM ?? $UNDEFINED !! to-roman( $sum ); +} + +#------------------------------------------------------------------------------- +sub subtract( Math::Roman:D $left-op, Math::Roman:D $right-op --> Str:D ) +#------------------------------------------------------------------------------- +{ + my Int $difference = $left-op - $right-op; + + return $difference == 0 ?? $ROM-NUM-ZERO !! + $difference < 0 ?? $UNDEFINED !! to-roman( $difference ); +} + +#------------------------------------------------------------------------------- +sub multiply( Math::Roman:D $left-op, Math::Roman:D $right-op --> Str:D ) +#------------------------------------------------------------------------------- +{ + my UInt $product = $left-op * $right-op; + + return $product > $MAX-ROM-NUM ?? $UNDEFINED !! to-roman( $product ); +} + +#------------------------------------------------------------------------------- +sub divide( Math::Roman:D $left-op, Math::Roman:D $right-op --> Str:D ) +#------------------------------------------------------------------------------- +{ + my Rat $quotient = $left-op / $right-op; + + return $quotient.denominator == 1 ?? to-roman( $quotient.Int ) !! + $UNDEFINED; +} + +#------------------------------------------------------------------------------- +sub raise-to-power( Math::Roman:D $left-op, Math::Roman:D $right-op --> Str:D ) +#------------------------------------------------------------------------------- +{ + my UInt $power = $left-op ** $right-op; + + return $power > $MAX-ROM-NUM ?? $UNDEFINED !! to-roman( $power ); +} + +#------------------------------------------------------------------------------- +sub is-roman-num( Str:D $str --> Bool:D ) +#------------------------------------------------------------------------------- +{ + my Bool $valid = True; + my Math::Roman $roman-num; + + { + CATCH + { + when X::TypeCheck::Return + { + $valid = False; + } + } + + $roman-num = Math::Roman.new: $str; + } + + $valid = False if $valid && $roman-num > $MAX-ROM-NUM; + + return $valid; +} + +#------------------------------------------------------------------------------- +sub is-operator( Str:D $str --> Bool:D ) +#------------------------------------------------------------------------------- +{ + for @OPERATORS -> Str $op + { + return True if $str eq $op; + } + + return False; +} + +#------------------------------------------------------------------------------- +sub run-tests() +#------------------------------------------------------------------------------- +{ + 'Running the test suite'.put; + + for test-data.lines -> Str $line + { + my Str ($test-name, $op1, $opr, $op2, $expected) = $line.split: / \| /; + + for $test-name, $op1, $opr, $op2, $expected + { + s/ ^ \s+ //; + s/ \s+ $ //; + } + + my Str $result = calculate( $op1, $opr, $op2 ); + + is $result, $expected, $test-name; + } + + done-testing; +} + +#------------------------------------------------------------------------------- +sub error( Str:D $message ) +#------------------------------------------------------------------------------- +{ + "ERROR: $message".put; + + USAGE(); + + exit 0; +} + +#------------------------------------------------------------------------------- +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 |IV |+ |V |IX + Example 2 |M |- |I |CMXCIX + Example 3 |X |/ |II |V + Example 4 |XI |* |VI |LXVI + Example 5 |VII |**|III|CCCXLIII + Example 6 |V |- |V |nulla + Example 7 |V |/ |II |non potest + Example 8 |MMM |+ |M |non potest + Example 9 |V |- |X |non potest + Large division|MMMCMXCVI|/ |IV |CMXCIX + END +} + +################################################################################ |
