aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMohammad S Anwar <Mohammad.Anwar@yahoo.com>2020-08-30 06:52:01 +0100
committerGitHub <noreply@github.com>2020-08-30 06:52:01 +0100
commit962396b1aeca4058990456005f9f69bfe78891fd (patch)
treeeb6fc53c0858a6d4c53f6748e578689bb0e5cb8e
parent38afe9eed7226ee88f6c5884a26035041af56082 (diff)
parent535f3755e029996ab915d8623fbd64c6de49336a (diff)
downloadperlweeklychallenge-club-962396b1aeca4058990456005f9f69bfe78891fd.tar.gz
perlweeklychallenge-club-962396b1aeca4058990456005f9f69bfe78891fd.tar.bz2
perlweeklychallenge-club-962396b1aeca4058990456005f9f69bfe78891fd.zip
Merge pull request #2168 from PerlMonk-Athanasius/branch-for-challenge-075
Perl & Raku solutions to Tasks 1 & 2 of the Perl Weekly Challenge #075
-rw-r--r--challenge-075/athanasius/perl/ch-1.pl165
-rw-r--r--challenge-075/athanasius/perl/ch-2.pl241
-rw-r--r--challenge-075/athanasius/raku/ch-1.raku155
-rw-r--r--challenge-075/athanasius/raku/ch-2.raku227
4 files changed, 788 insertions, 0 deletions
diff --git a/challenge-075/athanasius/perl/ch-1.pl b/challenge-075/athanasius/perl/ch-1.pl
new file mode 100644
index 0000000000..80426d36d0
--- /dev/null
+++ b/challenge-075/athanasius/perl/ch-1.pl
@@ -0,0 +1,165 @@
+#!perl
+
+################################################################################
+=comment
+
+Perl Weekly Challenge 075
+=========================
+
+Task #1
+-------
+*Coins Sum*
+
+Submitted by: Mohammad S Anwar
+
+You are given a set of coins _@C_, assuming you have infinite amount of each
+coin in the set.
+
+Write a script to find how many ways you make sum _$S_ using the coins from the
+set _@C_.
+
+Example:
+
+Input:
+ @C = (1, 2, 4)
+ $S = 6
+
+Output: 6
+There are 6 possible ways to make sum 6.
+a) (1, 1, 1, 1, 1, 1)
+b) (1, 1, 1, 1, 2)
+c) (1, 1, 2, 2)
+d) (1, 1, 4)
+e) (2, 2, 2)
+f) (2, 4)
+
+=cut
+################################################################################
+
+#--------------------------------------#
+# Copyright © 2020 PerlMonk Athanasius #
+#--------------------------------------#
+
+ # Exports:
+use strict;
+use warnings;
+use Const::Fast; # const()
+use Getopt::Long; # GetOptions()
+use Memoize; # memoize()
+use Regexp::Common qw( number ); # %RE{num}
+
+const my $USAGE =>
+"Usage:
+ perl $0 [-S=<Natural>] [<C> ...]
+
+ -S=<Natural> Target coin sum
+ [<C> ...] Non-empty set of coin denominations (Naturals <= S)\n";
+
+#-------------------------------------------------------------------------------
+BEGIN
+#-------------------------------------------------------------------------------
+{
+ $| = 1;
+ print "\nChallenge 075, Task #1: Coins Sum (Perl)\n\n";
+}
+
+#===============================================================================
+MAIN:
+#===============================================================================
+{
+ my ($S, @C) = parse_command_line();
+
+ # Ensure that @C is a *set* by removing any duplicate coin values; also sort
+ # the values
+
+ my %C = map { $_ => undef } @C;
+ @C = sort { $a <=> $b } keys %C;
+
+ # For non-trivial cases, memoization vastly decreases computation time
+
+ memoize('count_coin_combinations');
+
+ # Reversing the coin array -- so that the coins are processed in decreasing
+ # order, largest coins first, smallest coins last -- significantly reduces
+ # the total number of recursive calls to count_coin_combinations()
+
+ my $total = count_coin_combinations($S, reverse @C);
+
+ printf "There %s %s possible way%s to make the sum %s from the coin%s %s\n",
+ $total == 1 ? 'is' : 'are',
+ add_commas($total),
+ $total == 1 ? '' : 's',
+ add_commas($S),
+ scalar @C == 1 ? '' : 's',
+ join ', ', @C;
+}
+
+#-------------------------------------------------------------------------------
+sub count_coin_combinations # Recursive function
+#-------------------------------------------------------------------------------
+{
+ my ($target, $coin, @coins) = @_;
+ my $sum = 0;
+
+ if (scalar @coins) # There are more coins to process
+ {
+ for my $i (0 .. int($target / $coin))
+ {
+ my $new_target = $target - ($i * $coin);
+
+ if ($new_target == 0) # Base case 1: target already reached
+ {
+ ++$sum;
+ }
+ else # Recursive case
+ {
+ $sum += count_coin_combinations($new_target, @coins);
+ }
+ }
+ }
+ else # Base case 2: no more coins
+ {
+ $sum = 1 if $target % $coin == 0;
+ }
+
+ return $sum;
+}
+
+#-------------------------------------------------------------------------------
+sub parse_command_line
+#-------------------------------------------------------------------------------
+{
+ my $S;
+
+ GetOptions('S=i' => \$S) or die $USAGE;
+
+ my @C = @ARGV;
+
+ scalar @C > 0 or die $USAGE;
+ is_natural($S) or die $USAGE;
+ is_natural($_) && $_ <= $S or die $USAGE for @C;
+
+ return ($S, @C);
+}
+
+#-------------------------------------------------------------------------------
+sub is_natural
+#-------------------------------------------------------------------------------
+{
+ my ($n) = @_;
+
+ return defined($n) && $n =~ / \A $RE{num}{int} \z /x && $n > 0;
+}
+
+#-------------------------------------------------------------------------------
+sub add_commas
+#-------------------------------------------------------------------------------
+{
+ my ($number) = @_;
+
+ # Regex from perlfaq5: "How can I output my numbers with commas added?"
+
+ return $number =~ s/(^\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/gr;
+}
+
+################################################################################
diff --git a/challenge-075/athanasius/perl/ch-2.pl b/challenge-075/athanasius/perl/ch-2.pl
new file mode 100644
index 0000000000..6bc930cc1b
--- /dev/null
+++ b/challenge-075/athanasius/perl/ch-2.pl
@@ -0,0 +1,241 @@
+#!perl
+
+################################################################################
+=comment
+
+Perl Weekly Challenge 075
+=========================
+
+Task #2
+-------
+*Largest Rectangle Histogram*
+
+Submitted by: Mohammad S Anwar
+
+You are given an array of positive numbers _@A_.
+
+Write a script to find the large[s]t rectangle histogram created by the given
+array.
+
+BONUS: Try to print the histogram as shown in the example, if possible.
+
+Example 1:
+
+Input: @A = (2, 1, 4, 5, 3, 7)
+
+ 7 #
+ 6 #
+ 5 # #
+ 4 # # #
+ 3 # # # #
+ 2 # # # # #
+ 1 # # # # # #
+ _ _ _ _ _ _ _
+ 2 1 4 5 3 7
+
+Looking at the above histogram, the largest rectangle (4 x 3) is formed by
+columns (4, 5, 3 and 7).
+
+Output: 12
+
+Example 2:
+
+Input: @A = (3, 2, 3, 5, 7, 5)
+
+ 7 #
+ 6 #
+ 5 # # #
+ 4 # # #
+ 3 # # # # #
+ 2 # # # # # #
+ 1 # # # # # #
+ _ _ _ _ _ _ _
+ 3 2 3 5 7 5
+
+Looking at the above histogram, the largest rectangle (3 x 5) is formed by
+columns (5, 7 and 5).
+
+Output: 15
+
+=cut
+################################################################################
+
+#--------------------------------------#
+# Copyright © 2020 PerlMonk Athanasius #
+#--------------------------------------#
+
+use strict;
+use warnings;
+use Const::Fast; # Exports const()
+use List::MoreUtils qw( pairwise );
+use List::Util qw( max );
+use Regexp::Common qw( number ); # Exports %RE{num}
+
+const my $USAGE =>
+"Usage:
+ perl $0 [<A> ...]
+
+ [<A> ...] Non-empty sequence of positive integers\n";
+
+const my $MAX_COLUMNS => 38; # (For my particular command-line screen setup)
+const my $MAX_HEIGHT => 31; # N.B.: The logic in print_histogram() below
+ # assumes that $MAX_HEIGHT < 100
+
+#-------------------------------------------------------------------------------
+BEGIN
+#-------------------------------------------------------------------------------
+{
+ $| = 1;
+ print "\nChallenge 075, Task #2: Largest Rectangle Histogram (Perl)\n\n";
+}
+
+#===============================================================================
+MAIN:
+#===============================================================================
+{
+ my @A = parse_command_line();
+
+ print_histogram(@A);
+
+ my $rect = find_largest_rectangle(@A);
+
+ if ($rect->{area} == 0)
+ {
+ print "\nThe histogram contains no rectangles\n\nArea: 0\n";
+ }
+ else
+ {
+ printf "\nThe largest rectangle (%d x %d) has corners at (C%d, R1) " .
+ "and (C%d, R%d)\n\nArea: %d\n",
+ @{$rect}{ qw( width row_r col_l col_r row_r area ) };
+ }
+}
+
+#===============================================================================
+=comment
+
+Assumptions
+-----------
+
+1. A single column (vertical bar) is a rectangle of width 1.
+2. If two or more rectangles have the maximum area, only the first to be found
+ is given as "the largest rectangle".
+
+Algorithm
+---------
+
+In a histogram, all bars are anchored in the first row, from which it follows
+that any candidate for largest rectangle must have 2 of its 4 corners in the
+first row. (If the rectangle is a single bar, its left and right lower corners
+are identical.) A rectangle can be uniquely specified by any 2 diagonally-
+opposite corners. In the solution below, these are the bottom left and top right
+corners.
+
+To check a given column C: for each row R in C, find the longest unbroken line
+of non-empty squares to the immediate right of (C, R). Suppose the line for row
+R ends in column D. Then the rectangle has corners (C, 1) and (D, R); the width
+is D - C + 1; and the height is R.
+
+Any column C to the right of the first needs to be checked iff A[c] > A[c-1],
+because otherwise it's already been included in a check for a previous column.
+By the same logic, if column C does need to be checked at all, only those rows >
+A[c-1] need be checked.
+
+=cut
+#===============================================================================
+
+#-------------------------------------------------------------------------------
+sub find_largest_rectangle
+#-------------------------------------------------------------------------------
+{
+ my @A = @_;
+ my @keys = qw( col_l col_r row_r width area );
+ my %max = map { $_ => 0 } @keys;
+
+ for my $col_l (0 .. $#A)
+ {
+ my $prev_row = $col_l ? $A[$col_l - 1] : 0;
+ my $this_row = $A[$col_l];
+
+ if ($col_l == 0 || $this_row > $prev_row)
+ {
+ for my $row ($prev_row + 1 .. $this_row)
+ {
+ my $width = 1;
+ my $col_r = $col_l;
+
+ INNER: for my $col ($col_l + 1 .. $#A)
+ {
+ if ($A[$col] >= $row)
+ {
+ ++$col_r;
+ ++$width;
+ }
+ else
+ {
+ last INNER;
+ }
+ }
+
+ if ((my $area = $width * $row) > $max{area})
+ {
+ my @new = ($col_l + 1, $col_r + 1, $row, $width, $area);
+ %max = pairwise { $a => $b } @keys, @new;
+ }
+ }
+ }
+ }
+
+ return \%max;
+}
+
+#-------------------------------------------------------------------------------
+sub print_histogram
+#-------------------------------------------------------------------------------
+{
+ my @A = @_;
+ my $columns = scalar @A;
+ my $max_height = max @A;
+
+ if ($columns <= $MAX_COLUMNS &&
+ $max_height <= $MAX_HEIGHT)
+ {
+ for my $row (reverse 1 .. $max_height)
+ {
+ printf " %2d", $row;
+ print $_ >= $row ? ' #' : ' ' for @A;
+ print "\n";
+ }
+
+ printf " _%s\n", ' _' x $columns;
+
+ if ($max_height < 10)
+ {
+ printf " %s\n", join ' ', @A;
+ }
+ else
+ {
+ printf " %s\n", join ' ', map { int($_ / 10) || ' ' } @A;
+ printf " %s\n", join ' ', map { $_ % 10 } @A;
+ }
+ }
+ else
+ {
+ printf "The histogram is too %s to print on a single screen\n",
+ $columns > $MAX_COLUMNS ? 'wide' : 'tall';
+ }
+}
+
+#-------------------------------------------------------------------------------
+sub parse_command_line
+#-------------------------------------------------------------------------------
+{
+ my @A = @ARGV;
+
+ scalar @A > 0 or die $USAGE;
+ defined($_) && /\A$RE{num}{int}\z/ && $_ >= 0 or die $USAGE for @A;
+
+ return @A;
+}
+
+################################################################################
diff --git a/challenge-075/athanasius/raku/ch-1.raku b/challenge-075/athanasius/raku/ch-1.raku
new file mode 100644
index 0000000000..ee196fe281
--- /dev/null
+++ b/challenge-075/athanasius/raku/ch-1.raku
@@ -0,0 +1,155 @@
+use v6d;
+
+################################################################################
+=begin comment
+
+Perl Weekly Challenge 075
+=========================
+
+Task #1
+-------
+*Coins Sum*
+
+Submitted by: Mohammad S Anwar
+
+You are given a set of coins _@C_, assuming you have infinite amount of each
+coin in the set.
+
+Write a script to find how many ways you make sum _$S_ using the coins from the
+set _@C_.
+
+Example:
+
+Input:
+ @C = (1, 2, 4)
+ $S = 6
+
+Output: 6
+There are 6 possible ways to make sum 6.
+a) (1, 1, 1, 1, 1, 1)
+b) (1, 1, 1, 1, 2)
+c) (1, 1, 2, 2)
+d) (1, 1, 4)
+e) (2, 2, 2)
+f) (2, 4)
+
+=end comment
+################################################################################
+
+#--------------------------------------#
+# Copyright © 2020 PerlMonk Athanasius #
+#--------------------------------------#
+
+use Memoize;
+
+subset Natural of UInt where * > 0;
+
+#-------------------------------------------------------------------------------
+BEGIN
+#-------------------------------------------------------------------------------
+{
+ "\nChallenge 075, Task #1: Coins Sum (Raku)\n".put;
+}
+
+##==============================================================================
+sub MAIN
+(
+ Natural:D :$S, #= Target coin sum
+ *@C where { @C.elems > 0 && #= Non-empty set of coin
+ @C.all ~~ Natural:D && #= denominations (Naturals <= S)
+ @C.all <= $S }
+)
+##==============================================================================
+{
+ # Ensure that @C is a *set* by removing any duplicate coin values; also sort
+ # the values
+
+ my Nil %C = @C.map: { $_ => Nil };
+
+ @C = %C.keys.sort( { .Int } ).map: { .UInt };
+
+ # For non-trivial cases, memoization vastly decreases computation time
+
+ memoize(&count-coin-combinations);
+
+ # Reversing the coin array -- so that the coins are processed in decreasing
+ # order, largest coins first, smallest coins last -- significantly reduces
+ # the total number of recursive calls to count_coin_combinations()
+
+ my UInt $total = count-coin-combinations($S, [ @C.reverse ]);
+
+ "There %s %s possible way%s to make the sum %s from the coin%s %s\n".printf:
+ $total == 1 ?? 'is' !! 'are',
+ add-commas($total),
+ $total == 1 ?? '' !! 's',
+ add-commas($S),
+ @C.elems == 1 ?? '' !! 's',
+ @C.join: ', ';
+}
+
+#-------------------------------------------------------------------------------
+sub count-coin-combinations
+(
+ Natural:D $target,
+ Array:D[Natural:D] $coins,
+--> UInt:D
+)
+#-------------------------------------------------------------------------------
+{
+ my UInt $sum = 0;
+
+ my Natural $coin = $coins.shift;
+
+ if $coins.elems > 0 # There are more coins to process
+ {
+ for 0 .. floor($target / $coin) -> UInt $i
+ {
+ my UInt $new-target = $target - ($i * $coin);
+
+ if $new-target == 0 # Base case 1: target already reached
+ {
+ ++$sum;
+ }
+ else # Recursive case
+ {
+ # Note: $coins is an Array object, and therefore a reference; to
+ # pass it by value -- as required here -- it is necessary to
+ # make a copy (clone); otherwise, the effect of shift() above
+ # will propagate to recursive calls higher (i.e., earlier) in
+ # the call hierarchy, leaving $coins in an incorrect state when
+ # those calls are eventually made.
+
+ $sum += count-coin-combinations($new-target, $coins.clone);
+ }
+ }
+ }
+ else # Base case 2: no more coins
+ {
+ $sum = 1 if $target % $coin == 0;
+ }
+
+ return $sum;
+}
+
+#-------------------------------------------------------------------------------
+sub USAGE()
+#-------------------------------------------------------------------------------
+{
+ my Str $usage = $*USAGE;
+
+ $usage ~~ s/ ($*PROGRAM-NAME) /raku $0/;
+ $usage.put;
+}
+
+#-------------------------------------------------------------------------------
+sub add-commas(UInt:D $number --> Str:D)
+#-------------------------------------------------------------------------------
+{
+ # From https://rosettacode.org/wiki/Commatizing_numbers#Raku
+
+ return $number.subst: :1st,
+ / <[ 1 .. 9 ]> <[ 0 .. 9 ]>* /,
+ *.flip.comb( /<{ '. ** 1..3' }>/ ).join( ',' ).flip;
+}
+
+################################################################################
diff --git a/challenge-075/athanasius/raku/ch-2.raku b/challenge-075/athanasius/raku/ch-2.raku
new file mode 100644
index 0000000000..c13c132541
--- /dev/null
+++ b/challenge-075/athanasius/raku/ch-2.raku
@@ -0,0 +1,227 @@
+use v6d;
+
+################################################################################
+=begin comment
+
+Perl Weekly Challenge 075
+=========================
+
+Task #2
+-------
+*Largest Rectangle Histogram*
+
+Submitted by: Mohammad S Anwar
+
+You are given an array of positive numbers _@A_.
+
+Write a script to find the large[s]t rectangle histogram created by the given
+array.
+
+BONUS: Try to print the histogram as shown in the example, if possible.
+
+Example 1:
+
+Input: @A = (2, 1, 4, 5, 3, 7)
+
+ 7 #
+ 6 #
+ 5 # #
+ 4 # # #
+ 3 # # # #
+ 2 # # # # #
+ 1 # # # # # #
+ _ _ _ _ _ _ _
+ 2 1 4 5 3 7
+
+Looking at the above histogram, the largest rectangle (4 x 3) is formed by
+columns (4, 5, 3 and 7).
+
+Output: 12
+
+Example 2:
+
+Input: @A = (3, 2, 3, 5, 7, 5)
+
+ 7 #
+ 6 #
+ 5 # # #
+ 4 # # #
+ 3 # # # # #
+ 2 # # # # # #
+ 1 # # # # # #
+ _ _ _ _ _ _ _
+ 3 2 3 5 7 5
+
+Looking at the above histogram, the largest rectangle (3 x 5) is formed by
+columns (5, 7 and 5).
+
+Output: 15
+
+=end comment
+################################################################################
+
+#--------------------------------------#
+# Copyright © 2020 PerlMonk Athanasius #
+#--------------------------------------#
+
+use List::UtilsBy <zip_by>;
+
+my UInt constant $MAX-COLUMNS = 38; # (For my particular command-line setup)
+my UInt constant $MAX-HEIGHT = 31; # N.B.: The logic in print-histogram()
+ # below assumes $MAX-HEIGHT < 100
+
+#-------------------------------------------------------------------------------
+BEGIN
+#-------------------------------------------------------------------------------
+{
+ "\nChallenge 075, Task #2: Largest Rectangle Histogram (Raku)\n".put;
+}
+
+##==============================================================================
+sub MAIN
+(
+ *@A where { @A.elems > 0 && #= Non-empty sequence of positive integers
+ @A.all ~~ UInt:D }
+)
+##==============================================================================
+{
+ print-histogram(@A);
+
+ my UInt %rect = find-largest-rectangle(@A);
+
+ if %rect<area> == 0
+ {
+ "\nThe histogram contains no rectangles\n\nArea: 0".put;
+ }
+ else
+ {
+ ("\nThe largest rectangle (%d x %d) has corners at " ~
+ "(C%d, R1) and (C%d, R%d)\n\nArea: %d\n").printf:
+ %rect<width row-r col-l col-r row-r area>;
+ }
+}
+
+#===============================================================================
+=begin comment
+
+Assumptions
+-----------
+
+1. A single column (vertical bar) is a rectangle of width 1.
+2. If two or more rectangles have the maximum area, only the first to be found
+ is given as "the largest rectangle".
+
+Algorithm
+---------
+
+In a histogram, all bars are anchored in the first row, from which it follows
+that any candidate for largest rectangle must have 2 of its 4 corners in the
+first row. (If the rectangle is a single bar, its left and right lower corners
+are identical.) A rectangle can be uniquely specified by any 2 diagonally-
+opposite corners. In the solution below, these are the bottom left and top right
+corners.
+
+To check a given column C: for each row R in C, find the longest unbroken line
+of non-empty squares to the immediate right of (C, R). Suppose the line for row
+R ends in column D. Then the rectangle has corners (C, 1) and (D, R); the width
+is D - C + 1; and the height is R.
+
+Any column C to the right of the first needs to be checked iff A[c] > A[c-1],
+because otherwise it's already been included in a check for a previous column.
+By the same logic, if column C does need to be checked at all, only those rows >
+A[c-1] need be checked.
+
+=end comment
+#===============================================================================
+
+#-------------------------------------------------------------------------------
+sub find-largest-rectangle(Array:D[UInt:D] $A --> Hash:D[UInt:D])
+#-------------------------------------------------------------------------------
+{
+ my Str @keys = <col-l col-r row-r width area>;
+ my UInt %max = @keys.map: { $_ => 0 };
+
+ for 0 .. $A.end -> UInt $col-l
+ {
+ my UInt $prev-row = $col-l ?? $A[$col-l - 1] !! 0;
+ my UInt $this-row = $A[$col-l];
+
+ if ($col-l == 0 || $this-row > $prev-row)
+ {
+ for $prev-row + 1 .. $this-row -> UInt $row
+ {
+ my UInt $width = 1;
+ my UInt $col-r = $col-l;
+
+ INNER: for $col-l + 1 .. $A.end -> UInt $col
+ {
+ if $A[$col] >= $row
+ {
+ ++$col-r;
+ ++$width;
+ }
+ else
+ {
+ last INNER;
+ }
+ }
+
+ if (my UInt $area = $width * $row) > %max<area>
+ {
+ %max = zip_by { |@_ }, @keys, ($col-l + 1, $col-r + 1, $row,
+ $width, $area);
+ }
+ }
+ }
+ }
+
+ return %max;
+}
+
+#-------------------------------------------------------------------------------
+sub print-histogram(Array:D[UInt:D] $A)
+#-------------------------------------------------------------------------------
+{
+ my UInt $columns = $A.elems;
+ my UInt $max-height = $A.max;
+
+ if $columns <= $MAX-COLUMNS &&
+ $max-height <= $MAX-HEIGHT
+ {
+ for (1 .. $max-height).reverse -> UInt $row
+ {
+ ' %2d'.printf: $row;
+ ' %s' .printf: $_ >= $row ?? '#' !! ' ' for $A.list;
+ ''.put;
+ }
+
+ " _%s\n".printf: ' _' x $columns;
+
+ if $max-height < 10
+ {
+ " %s\n".printf: $A.join: ' ';
+ }
+ else
+ {
+ " %s\n".printf: $A.map( { ($_ / 10).floor || ' ' } ).join: ' ';
+ " %s\n".printf: $A.map( { $_ % 10 } ).join: ' ';
+ }
+ }
+ else
+ {
+ "The histogram is too %s to print on a single screen\n".printf:
+ $columns > $MAX-COLUMNS ?? 'wide' !! 'tall';
+ }
+}
+
+#-------------------------------------------------------------------------------
+sub USAGE()
+#-------------------------------------------------------------------------------
+{
+ my Str $usage = $*USAGE;
+
+ $usage ~~ s/ ($*PROGRAM-NAME) /raku $0/;
+ $usage.put;
+}
+
+################################################################################