aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMohammad S Anwar <Mohammad.Anwar@yahoo.com>2021-10-15 12:32:16 +0100
committerGitHub <noreply@github.com>2021-10-15 12:32:16 +0100
commitb20aa9b1c95c89fd1979bb5515f397913d6b1bc7 (patch)
treedda28d54bb15d355df4bd4aadebcbbf7629706b8
parent5365a29aecdece7055d66b81e19aad7d8ecfed61 (diff)
parent17f80bf213692c67f843cffe008e9e95401c4b8e (diff)
downloadperlweeklychallenge-club-b20aa9b1c95c89fd1979bb5515f397913d6b1bc7.tar.gz
perlweeklychallenge-club-b20aa9b1c95c89fd1979bb5515f397913d6b1bc7.tar.bz2
perlweeklychallenge-club-b20aa9b1c95c89fd1979bb5515f397913d6b1bc7.zip
Merge pull request #5025 from drbaggy/master
Write up
-rw-r--r--challenge-134/james-smith/README.md191
-rw-r--r--challenge-134/james-smith/perl/ch-1.pl34
2 files changed, 51 insertions, 174 deletions
diff --git a/challenge-134/james-smith/README.md b/challenge-134/james-smith/README.md
index 188174521f..1d17e5726f 100644
--- a/challenge-134/james-smith/README.md
+++ b/challenge-134/james-smith/README.md
@@ -1,4 +1,4 @@
-# Perl Weekly Challenge #133
+# Perl Weekly Challenge #134
You can find more information about this weeks, and previous weeks challenges at:
@@ -10,180 +10,57 @@ submit solutions in whichever language you feel comfortable with.
You can find the solutions here on github at:
-https://github.com/drbaggy/perlweeklychallenge-club/tree/master/challenge-133/james-smith/perl
+https://github.com/drbaggy/perlweeklychallenge-club/tree/master/challenge-134/james-smith/perl
-# Task 1 - Integer Square Root
+# Task 1 - Pandigital Numbers
-***You are given a positive integer `$N`. Write a script to calculate the integer square root of the given number.***
+***Write a script to generate first 5 Pandigital Numbers in base 10.***
## The solution
-We implement Newton's formulae to compute the integer square root.. This is the compact version.
+Pandigital numbers (base 10) are numbers who contain all the digits between 0-9 (but don't start with 0)
+The lowest pandigital numbers all are permutations of the digits 0..9 with 0 not being the first digit.
-```perl
-sub find_root {
- my( $x, $y ) = ( my $n = shift ) >> 1; ## $x is half $n rounded down...
- return $n unless $x; ## Return $n if it is <2 (i.e. 0 or 1)
- $x = $y while ( $y = ( $x + $n / $x ) >> 1 ) < $x; ## The crux - the next no is 1/2 of $x & $n/$x
- return $x; ## Return value
-}
-```
+To walk the pandigital numbers we can write a script which generates the next perumtation in "lexical" order. You don't need to do this recursively or with tightly nested loops a simple algorithm finds them...
-# Task 2 - Smith Numbers
+ * Find the largest value of `$i` such that `$s[$i]` `<` `$s[$i+1]`
+ * Find the largest value of `$j` such that `$s[$i]` `<` `$s[$j]`
+ * Flip these to entries
+ * Flip all entries from $i+1 to end of the list...
-***Write a script to generate first 10 Smith Numbers in base 10.***
-
-## Solution
+We note though that we don't have to start at 0123456789 (the lowest permutation) as all numbers starting with 0 are skipped. We can then pre-empty this loop by noting the largest permutation 0987654321 which isn't a pan-digital number, so when we find the next iteration we have our first pandigital number....
-We break the logic up into chunks and implement those in separate function. As we look at each
-prime in turn we can effectively create a sieve to get the prime factors.
-
-Here is the main code.
- * We check to see if the sum of digits is the same as the sum of primefactor digits {and that it is not prime}
- * We add this to the list if it is the case
- * Return when we have `$C` (`10`) in this case...
```perl
-sub smith_numbers {
- my ( $C, $n, @sn ) = (shift,3);
- ( sum_digits( $n ) - sum map { sum_digits $_ } prime_factors( $n ) ) ||
- ( push @sn, $n ) &&
- ( @sn == $C ) &&
- ( return @sn ) while $n++;
+my @s = reverse 1..9, 0;
+
+sub next_perm {
+ my( $i, $j );
+ ( $s[$_] < $s[$_+1] ) && ( $i = $_ ) foreach 0 .. @s-2;
+ return unless defined $i;
+ ( $s[$i] < $s[$_] ) && ( $j = $_ ) foreach $i+1 .. @s-1;
+ @s[ $i, $j ] = @s[ $j, $i ];
+ @s[ $i+1 .. @s-1 ] = @s[ reverse $i+1 .. @s-1 ];
+ return 1;
}
-```
-
-And the support functions:
- * `sum` - Just sum an array;
- * `sum_digits` - Sum digits of number,
- We cache the digit sum for each number in `%ds`;
- * `prime_factors` - for a prime returns nothing, for a composite returns the factors,
- We keep a list of primes `@primes`, and also the prime factors for each composite `%comp`.
- **Note:** We don't need to continue with primes bigger than `sqrt($N)` so short cut the loop.
-
-```perl
-sub sum { my $t = 0; $t+=$_ foreach @_; $t; }
-
-sub sum_digits { return $ds{$_[0]} ||= sum split //, $_[0]; }
-sub prime_factors {
- my $N = shift;
- ( $N % $_) ? ( ( $N < $_ * $_ ) && last )
- : ( return $sum_pf[$N] = $sum_pf[$N/$_] + $sum_pf[$_] ) foreach @primes;
- $sum_pf[$N] = cl_sum_digits $N;
- push @primes, $N;
- return 0;
-}
+say @s while next_perm && $count--;
```
-## Performance
-
-A slight modification to the code ( to compute Smith Numbers < N ) computes the 29,928 Smith numbers less than 1 million in about 3.3 seconds, and for 10,000,000 about 45 seconds. For large N the script falls over with out-of-memory errors.
-
-## Re-write in C
-
-The algorithm does lend itself to rewriting in a lower level language - it doesn't use any nice features of Perl except for the "auto-sizing" of arrays - no hashes, complex IO etc.
-
-So here is my first C programme for probably 30 years!
-
-### Timings for C vs perl
-
-| Max value | Count | Time C | Time perl |
-| ------------: | ---------: | ------: | --------: |
-| 100 | 6 | 0.00s | 0.01s |
-| 1,000 | 49 | 0.00s | 0.01s |
-| 10,000 | 376 | 0.00s | 0.03s |
-| 100,000 | 3,924 | 0.01s | 0.26s |
-| 1,000,000 | 29,928 | 0.07s | 3.25s |
-| 10,000,000 | 278,411 | 1.18s | 45.38s |
-| 100,000,000 | 2,632,758 | 23.65s | *fail* |
-| 1,000,000,000 | 25,154,060 | 514.17s | *fail* |
+# Task 2 - Distinct Terms Count
-This is the limit for the algorithm (would need 20+ Gbytes for the 10 billion case)
+***You are given 2 positive numbers, `$m` and `$n`. Write a script to generate multiplcation table and display count of distinct terms.***
-Note there is an additional about 0.08 second penalty for compiling the C code - so infact for less than 100,000 the perl code is faster in total...
-
-### The C code
-
-```c
-#include <stdio.h>
-
-// Compute all Smith Numbers below MAX_N
-
-// Use guess that PSIZE should approximately 1.1 * PFSIZE / ln(PFSIZE)
-
-// 10^6
- #define MAX_N 1000000
- #define PSIZE 42000 // Have to guess this!
-// 10^7
-// #define MAX_N 10000000
-// #define PSIZE 400000 // Have to guess this!
-// 10^8
-// #define MAX_N 100000000
-// #define PSIZE 3200000 // Have to guess this!
-// 10^9
-// #define MAX_N 1000000000
-// #define PSIZE 28000000 // Have to guess this!
-
-#define PFSIZE (MAX_N/2)
-
-// Set up arrays
-int sum_pf[ PFSIZE+1 ], primes[ PSIZE ], prime_index = 0;
-
-// Compute sum of digits - unlike Perl we can't use split
-// so we use repeated modulus/divide by 10..
-
-int sum_digits(int n) {
- int total = 0;
- do { total += n%10; } while( n /= 10 );
- return total;
-}
+## Solution
-// Get the sum of prime factors -
-// as we build this in order we only need to find a
-// factorisation then we just add together the
-// digit sum of the two factors (Here for speed we
-// know one will be prime.
-// We go through all primes we have until prime^2
-// is greater than the number itself.
-//
-// To make the last bit easier IF we have a prime
-// we return 0 as not composite...
-//
-// Note to save memory we only store the sum if
-// n < MAX_N/2 as we won't need it again (can't
-// be a factor of a larger number less than MAX_N
-
-int sum_prime_factors( int n ) {
- int p;
- for(int i=0; i< prime_index; i++ ) {
- p = primes[i];
- if( !(n % p) ) {
- if( n <= PFSIZE ) {
- return sum_pf[n] = sum_pf[n/p] + sum_pf[p];
- } else {
- return sum_pf[ n/p ] + sum_pf[ p ];
- }
- }
- if( n < p*p ) { break; }
- }
- if( n <= PFSIZE ) {
- sum_pf[ n ] = sum_digits(n);
- primes[ prime_index++ ] = n;
- }
- return 0;
-}
+Number 2 again is the easier code this week...
+We just loop through the two indicies and make a note of each product as a keys of a hash. And return scalar
-// Main is simple just loop and search, printing out
-// Smith numbers
-int main() {
- int count = 0, n = 1;
- while( n++ <= MAX_N ) {
- if( sum_digits(n) == sum_prime_factors(n) ) {
- printf( "%11d\n", n );
- }
+```perl
+ my($m,$n,%x) = @_;
+ for my $i (1..$m) {
+ $x{$i*$_}++ for 1..$n;
}
-}
+ return scalar keys %x;
```
-**Notes:** In this version we use an optimisation - we know that we will never need to use factorisations or primes greater than `MAX_N/2` so we don't store/cache these.
-
+If `$m` & `$n` are large (and similar) there may be gain in separating the rectangle into a square and a rectangle - and only compute products for one half of the triangle...
diff --git a/challenge-134/james-smith/perl/ch-1.pl b/challenge-134/james-smith/perl/ch-1.pl
index d18ca48222..d8d09d9c4f 100644
--- a/challenge-134/james-smith/perl/ch-1.pl
+++ b/challenge-134/james-smith/perl/ch-1.pl
@@ -8,30 +8,30 @@ use Test::More;
use Benchmark qw(cmpthese timethis);
use Data::Dumper qw(Dumper);
-my $S = 10;
-my @s = (1,0,2..$S-1); ## Cheat we jump into the first pemutation which
- ## doesn't start with a "0" 1023456789 - avoids
- ## us having to check for a leading 0...
-my $count=5;
-
-do {
- say @s;
-} while next_perm && --$count;
-
+my @s = reverse 1..9,0; ## Cheat we start with the last perumation
+ ## starting with 0 - 0987654321
+ ## Saves us looping through the first
+ ## combinations checking for number starting
+ ## with non-zero (362880 combinations)
+my $count = @ARGV ? $ARGV[0] : 5;
sub next_perm {
- my($i,$j,$p);
+ my( $i, $j );
+
## Find largest index for which Si+1 > Si
- ( $s[$_] < $s[$_+1] ) && ( $i=$_ ) foreach 0 .. $S-2; ## Find i
+ ( $s[$_] < $s[$_+1] ) && ( $i = $_ ) foreach 0 .. @s-2; ## Find i
return unless defined $i; ## Got to the end of the list of permutations
## Find latest index for which Sj > Si for j>i
- ( $s[$i] < $s[$_] ) && ( $j=$_ ) foreach $i+1 .. $S-1; ## Find j
+ ( $s[$i] < $s[$_] ) && ( $j = $_ ) foreach $i+1 .. @s-1; ## Find j
- ## Flip i & jth elements..., then all numbers greater than i..
- ( $s[$i], $s[$j] ) = ( $s[$j], $s[$i] ); ## Flip numbers over...
- @s[ $i+1 .. $S-1 ] = @s[ reverse $i+1 .. $S-1 ]; ## Flip remaining list
- return 1;
+ ## Flip ith & jth elements..., then all numbers greater than i..
+ @s[ $i, $j ] = @s[ $j, $i ];
+ @s[ $i+1 .. @s-1 ] = @s[ reverse $i+1 .. @s-1 ];
+
+ return 1; ## Return true to say can continue...
}
+say @s while next_perm && $count--;
+