aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xchallenge-134/mattneleigh/perl/ch-1.pl202
-rwxr-xr-xchallenge-134/mattneleigh/perl/ch-2.pl214
2 files changed, 416 insertions, 0 deletions
diff --git a/challenge-134/mattneleigh/perl/ch-1.pl b/challenge-134/mattneleigh/perl/ch-1.pl
new file mode 100755
index 0000000000..6929a26475
--- /dev/null
+++ b/challenge-134/mattneleigh/perl/ch-1.pl
@@ -0,0 +1,202 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use English;
+
+################################################################################
+# Begin main execution
+################################################################################
+
+my $quantity = 5;
+
+print("The first ", $quantity, " pandigital numbers in Base 10 are:\n");
+foreach(find_pandigital_numbers($quantity)){
+ print(" $_\n");
+}
+
+exit(0);
+################################################################################
+# End main execution; subroutines follow
+################################################################################
+
+
+
+################################################################################
+# Find the first N pandigital numbers in Base 10
+# Takes one argument:
+# * The number N of pandigital numbers to find
+# Returns:
+# * A list of N pandigital numbers in Base 10, in ascending order
+################################################################################
+sub find_pandigital_numbers{
+ my $n = shift();
+
+ my $digit_count;
+ my @digits;
+ my @permutations = ();
+ my @pandigitals = ();
+
+ # Cheat (a little) by setting up the
+ # smallest pandigital number in Base 10 to
+ # start with
+ @digits = (1, 0, 2, 3, 4, 5, 6, 7, 8, 9);
+
+ # The inverse factorial of the quantity of
+ # numbers we're expected to find will tell
+ # us how many digits need to be permuted;
+ # calculate this and make sure the value
+ # is sane
+ $digit_count = inverse_factorial($n);
+ if($digit_count > scalar(@digits)){
+ $digit_count = scalar(@digits);
+ }
+
+ # Gather a list of permutations involving
+ # only the requisite number of digits at the
+ # end of the list- it saves some memory to
+ # do it this way
+ permute_list_sequence(
+ [ @digits[$#digits - $digit_count + 1 .. $#digits] ],
+ 0,
+ $digit_count,
+ \@permutations
+ );
+
+ if($digit_count == scalar(@digits)){
+ # All the digits were used for
+ # permutations; build strings out of the
+ # permuted digits.
+ # Assignment in conditional is deliberate
+ while(my $permutation = shift(@permutations)){
+ # Most definitions of a pandigital number
+ # don't permit leading zeros, so skip any
+ # permutations that have ended up with
+ # one; this should only happen if all
+ # available digits were permuted
+ next if($permutation->[0] == 0);
+ $permutation = join(
+ "",
+ @{$permutation}
+ );
+ push(@pandigitals, $permutation);
+ }
+ } else{
+ # There are unpermuted digits; build
+ # strings out of the unpermuted and permuted
+ # digits.
+ # Assignment in conditional is deliberate
+ while(my $permutation = shift(@permutations)){
+ $permutation = join(
+ "",
+ @digits[0 .. $#digits - $digit_count],
+ @{$permutation}
+ );
+ push(@pandigitals, $permutation);
+ }
+ }
+
+ # The numbers likely won't be in ascending
+ # order, however; sort them and return the
+ # desired quantity
+ $n--;
+ return(
+ (sort(@pandigitals))[
+ 0 .. ($n < $#pandigitals ? $n : $#pandigitals)
+ ]
+ );
+
+}
+
+
+
+################################################################################
+# Generate a sequence of permutations based on the contents of a supplied
+# array. Permutations will take place within the specified range of cells,
+# which must be within the array or undefined behavior may result. The
+# permutations may not be generated in any particularly meaningful order.
+# Takes four arguments:
+# * A ref to an array of cells to permute. NOTE: this array will be modified
+# as the permutations are generated; see the output array below for a copy of
+# the array in its original order
+# * The index of the first cell (base) of the active permutation region
+# * The number of cells, including the base, that will be part of the active
+# permutation region
+# * A ref to an array in which the permutations are to be stored as output;
+# this will be populated with permuted copies of the entire original input
+# array, including cells outside the active permutation region, if any. The
+# first array in this list will contain the cells in their unpermuted order.
+# Returns:
+# * undef at all times, which conveys no meaningful information
+# Adapted from Heap's Algorithm as described here
+# https://www.geeksforgeeks.org/heaps-algorithm-for-generating-permutations/
+################################################################################
+sub permute_list_sequence{
+ my $list = shift();
+ my $base = shift();
+ my $region_size = shift();
+ my $permutations = shift();
+
+ # Active region is just one cell-
+ # store a copy of the current list
+ if($region_size == 1){
+ push(@{$permutations}, [ @{$list} ]);
+ return();
+ }
+
+ for(0 .. $region_size - 1){
+ # Recurse deeper with a smaller active
+ # region
+ permute_list_sequence(
+ $list,
+ $base,
+ $region_size - 1,
+ $permutations
+ );
+
+ if($region_size % 2){
+ # Odd range size- swap first and last
+ # elements in the active region
+ @{$list}[$base, $base + $region_size - 1]
+ = @{$list}[$base + $region_size - 1, $base];
+ } else{
+ # Even range size- swap the $_th and
+ # last elements in the active region
+ @{$list}[$base + $_, $base + $region_size - 1]
+ = @{$list}[$base + $region_size - 1, $base + $_];
+ }
+ }
+
+ return();
+
+}
+
+
+
+################################################################################
+# Calculate the inverse factorial of an integer (i.e. given y, solve x! = y for
+# x). If the integer is not actually a factorial, the number calculated is the
+# inverse of the next largest factorial (e.g. an input of 10 yields 4; 3! = 6
+# while 4! = 24)
+# Takes one argument:
+# * A factorial whose inverse is to be calculated
+# Returns:
+# * The inverse of the factorial (or next largest factorial if the input is not
+# a factorial)
+################################################################################
+sub inverse_factorial{
+ my $y = int(shift());
+
+ my $x = 1;
+
+ while($y > 1){
+ $x++;
+ $y /= $x;
+ }
+
+ return($x);
+
+}
+
+
+
diff --git a/challenge-134/mattneleigh/perl/ch-2.pl b/challenge-134/mattneleigh/perl/ch-2.pl
new file mode 100755
index 0000000000..57c0ab142c
--- /dev/null
+++ b/challenge-134/mattneleigh/perl/ch-2.pl
@@ -0,0 +1,214 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use English;
+
+################################################################################
+# Begin main execution
+################################################################################
+
+my @dimensions = (
+ # Given cases
+ [3, 3],
+ [3, 5],
+
+ # Additional test cases
+ [10, 10]
+);
+
+foreach(@dimensions){
+ my $table = generate_multiplication_table(@{$_});
+
+ printf("Input: \$m = %d, \$n = %d\n", $_->[0], $_->[1]);
+ printf("Output:\n\n");
+
+ foreach(@{$table->{table}}){
+ print(" $_\n");
+ }
+ print("\n");
+
+ print("Distinct Terms: ", join(", ", @{$table->{distinct_terms}}) ,"\n");
+ print("Count: ", scalar(@{$table->{distinct_terms}}), "\n");
+ print("\n\n");
+}
+
+exit(0);
+################################################################################
+# End main execution; subroutines follow
+################################################################################
+
+
+
+################################################################################
+# Create a multiplication table containing a specified number of columns and
+# rows
+# Takes two arguments:
+# * An integer M that defines how many rows will be in the table; this must be
+# at least two (2)
+# * An integer N that defines how many columns will be in the table; this must
+# be at least two (2)
+# Returns on success:
+# * A ref to a hash containing the following fields:
+# {
+# table => [], # A list of formatted strings comprising the
+# # multiplication table with M * N products,
+# # ready for output
+# distinct_terms => [], # A list of distinct products contained within
+# # the table
+# }
+# Returns on error:
+# * undef if either M or N is smaller than two (2)
+################################################################################
+sub generate_multiplication_table{
+ my $m = int(shift());
+ my $n = int(shift());
+
+ my $N;
+ my $max_factor_width;
+ my $max_product_width;
+ my %output = ();
+
+ # Impose some notion of sanity on our
+ # input values
+ return(undef) if(($m < 2) || ($n < 2));
+
+ $output{table} = [];
+ $output{distinct_terms} = ();
+
+ # The maximum factor width will be
+ # determined by the initial value of $m;
+ # the width required for factors from $n
+ # is determined by the maximum product
+ # width, which is $m * $n
+ $max_factor_width = count_integer_characters($m);
+ $max_product_width = count_integer_characters($m * $n);
+
+ $N = $n;
+ while($m--){
+ my @row = ( );
+
+ $n = $N;
+ while($n--){
+ my $term = ($m + 1) * ($n + 1);
+
+ # Store the calculated product in this
+ # row, and make a note of it in the
+ # distinct terms table
+ unshift(@row, $term);
+ $output{distinct_terms}{$term} = 1;
+ }
+
+ # Include the $m factor for the vertical
+ # axis that will appear in the table
+ unshift(@row, $m + 1);
+
+ # Make a nicely formatted row for output
+ # and store it
+ unshift(
+ @{$output{table}},
+ format_table_row(\@row, $max_factor_width, $max_product_width)
+ );
+
+ unless($m){
+ # The last (top) row of products has
+ # been calculated; add a couple more
+ # things to the top of the table:
+ unshift(
+ @{$output{table}},
+
+ # A row of $N factors for the horizontal
+ # axis...
+ format_table_row(
+ [ "x", (1 .. $N) ],
+ $max_factor_width,
+ $max_product_width
+ ),
+
+ # And a horizontal rule
+ "-" x ($max_factor_width + 1)
+ .
+ "+"
+ .
+ "-" x (($max_product_width + 1) * $N)
+ );
+ }
+
+ } # end while($m--)
+
+ # Store the distinct terms for output-
+ # convert the distinct terms table into
+ # a list of its former keys
+ $output{distinct_terms} = [
+ sort({ $a <=> $b } keys(%{$output{distinct_terms}}))
+ ];
+
+ return(\%output);
+
+}
+
+
+
+################################################################################
+# Format a multiplication table row for output
+# Takes three arguments:
+# * A ref to an array that must contain the M factor for that row and its
+# products with all values of N (e.g [ 4, 4, 8, 12, 16 ] )
+# * The calculated width of the maximum value of M (e.g. 1)
+# * The calculated width of the maximum of all the products of M and N (e.g. 2)
+# Returns:
+# * A formatted string suitable for the body of a multiplicatino table, with a
+# vertical bar between the factor and its appropriately-spaced products
+# (e.g. "4 | 4 8 12 16")
+################################################################################
+sub format_table_row{
+ my $row = shift();
+ my $max_factor_width = shift();
+ my $max_term_width = shift();
+
+ return(
+ sprintf("%".$max_factor_width."s ", $row->[0])
+ .
+ "|"
+ .
+ join(
+ "",
+ map(
+ { sprintf(" %".$max_term_width."s", $_) }
+ @{$row}[1 .. $#$row]
+ )
+ )
+ );
+
+}
+
+
+
+################################################################################
+# Calculate the number of characters required to display an integer
+# Takes one argument:
+# * The number to examine
+# Returns:
+# * The number of characters required to display the provided number
+# NOTE: If a non-integer number is provided, the number of characters required
+# to display just the integer portion of the number will be calculated; if a
+# negative number is provided, the character required to display the sign will
+# be accounted for in the value returned
+################################################################################
+sub count_integer_characters{
+
+ return(
+ int(
+ log(abs($ARG[0])) / log(10)
+ +
+ ($ARG[0] < 0 ? 2 : 1)
+ +
+ # Fudge because of round-off error...
+ 0.000000000000001
+ )
+ );
+
+}
+
+
+