aboutsummaryrefslogtreecommitdiff
path: root/challenge-147
diff options
context:
space:
mode:
authorPeter Campbell Smith <pj.campbell.smith@gmail.com>2022-01-11 22:25:03 +0000
committerPeter Campbell Smith <pj.campbell.smith@gmail.com>2022-01-11 22:25:03 +0000
commitca2a0fbde12f6e80eb035ff7c574ac3d7bcd67bc (patch)
tree27cd8b3d95a9e03a2988afb2e3c47a177db005bc /challenge-147
parent08a76ad16ee62b2cbb2cda3508445047f2ff9cf1 (diff)
downloadperlweeklychallenge-club-ca2a0fbde12f6e80eb035ff7c574ac3d7bcd67bc.tar.gz
perlweeklychallenge-club-ca2a0fbde12f6e80eb035ff7c574ac3d7bcd67bc.tar.bz2
perlweeklychallenge-club-ca2a0fbde12f6e80eb035ff7c574ac3d7bcd67bc.zip
My solutions to week 147
Diffstat (limited to 'challenge-147')
-rw-r--r--challenge-147/peter-campbell-smith/blog.txt1
-rwxr-xr-xchallenge-147/peter-campbell-smith/perl/ch-1.pl70
-rwxr-xr-xchallenge-147/peter-campbell-smith/perl/ch-2.pl128
3 files changed, 199 insertions, 0 deletions
diff --git a/challenge-147/peter-campbell-smith/blog.txt b/challenge-147/peter-campbell-smith/blog.txt
new file mode 100644
index 0000000000..78e76441f9
--- /dev/null
+++ b/challenge-147/peter-campbell-smith/blog.txt
@@ -0,0 +1 @@
+https://pjcs-pwc.blogspot.com/2022/01/chop-off-their-heads-and-conquer.html
diff --git a/challenge-147/peter-campbell-smith/perl/ch-1.pl b/challenge-147/peter-campbell-smith/perl/ch-1.pl
new file mode 100755
index 0000000000..0a3901fe78
--- /dev/null
+++ b/challenge-147/peter-campbell-smith/perl/ch-1.pl
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+
+# Peter Campbell Smith - 2022-01-10
+# PWC 147 task 1
+
+use v5.28;
+use warnings;
+use strict;
+
+# Write a script to generate first 20 left-truncatable prime numbers in base 10.
+# In number theory, a left-truncatable prime is a prime number which, in a given base,
+# contains no 0, and if the leading left digit is successively removed, then all
+# resulting numbers are primes.
+
+# Note that the last digit cannot be 2, 4, 5, 6, 8, 0 as no primes end with those
+# and cannot be 9 as when all the preceding digits are removed, 9 is not prime,
+# so the last digit can only be 1, 3 or 7.
+
+# The definition above speaks of 'all resulting numbers' so I am ruling out
+# single-digit prime numbers as they have no 'resulting numbers' when the
+# leftmost digit is removed.
+
+my ($seeking, $prime_index, $from, $to, $test, $this, @not_a_prime, $string, $count,
+ $start, $factor, $multiple, $secs);
+
+# initialise
+$seeking = 20; # how many to find
+$count = 0; # how many found
+
+# find primes in ranges of 1000
+$secs = time;
+for ($from = 1; ; $from += 1000) {
+ $to = $from + 999;
+
+ # loop over all the possible factors, ie primes < sqrt($to)
+ for $factor (2 .. int(sqrt($to))) {
+ next if defined $not_a_prime[$factor]; # already known as not a prime
+
+ # mark all the multiples of $factor as non-primes (sieve of Eratosthenes)
+ $start = int($from / $factor); # multiples less than $start have already been done
+ $start = 2 if $start < 2;
+ for ($multiple = $start; $factor * $multiple <= $to; $multiple ++) {
+ $not_a_prime[$factor * $multiple] = 1;
+ }
+ }
+
+ # now test the primes in this range for left-truncatability
+ TEST: for $test ($from .. $to) {
+
+ # remove ineligibles - not prime, is a single digit, contains 0 or ends in 9
+ next if (defined $not_a_prime[$test] or $test =~ m|0| or $test =~ m|9$| or $test < 11);
+
+ # test for left-truncatability and construct string showing proof
+ $this = $test;
+ $string = qq[$this];
+
+ # remove successive left digits and test the residue for primeness
+ while ($this =~ s|\d(\d+)|$1|) {
+ next TEST if $not_a_prime[$this];
+ $string .= qq[ > $this];
+ }
+
+ # an answer!
+ say $string;
+ if (++ $count >= $seeking) {
+ say '' . (time - $secs) . qq[ seconds\n];
+ exit;
+ }
+ }
+}
diff --git a/challenge-147/peter-campbell-smith/perl/ch-2.pl b/challenge-147/peter-campbell-smith/perl/ch-2.pl
new file mode 100755
index 0000000000..e10b24053a
--- /dev/null
+++ b/challenge-147/peter-campbell-smith/perl/ch-2.pl
@@ -0,0 +1,128 @@
+#!/usr/bin/perl
+
+# Peter Campbell Smith - 2022-01-10
+# PWC 147 task 2
+
+use v5.28;
+use warnings;
+use strict;
+
+# Write a script to find the first pair of Pentagon Numbers whose sum and difference
+# are also a Pentagon Number. Pentagon numbers can be defined as P(n) = n(3n - 1)/2.
+
+my $seeking = 1;
+# first_method(); # see blog - it works but is slow
+second_method(); # this is better
+
+#---
+
+sub first_method {
+
+ # first method
+ my ($found, $n, $pentagon, %p, @f, $m, $diff, %queue, $sum, $mm, $nn, $sum2, $start);
+
+ $start = time;
+ $found = 0;
+
+ for ($n = 1; ; $n ++) {
+
+ # generate pentagon numbers sequentially
+ $pentagon = $n * (3 * $n - 1) / 2;
+ $p{$pentagon} = $n; # so for any $j <= $n, $j is a pentagon number if $p{$j}
+ $f[$n] = $pentagon; # and the $jth pentagon number is $f[$j]
+ next if $n == 1;
+
+ # check the difference between this pentagon number ($n) and all smaller ones ($m)
+ for $m (1 .. $n - 1) {
+ $diff = $pentagon - $f[$m];
+ next unless $p{$diff}; # difference is not a pentagon number
+
+ # the difference is pentagonal; the sum will be more than $pentagon so put it in a queue to be checked later
+ $queue{sprintf('%012d', $f[$m] + $pentagon)} = [$m, $n]; # zero padded key so they sort numerically
+
+ # test per wikipedia
+ $sum = $f[$m] + $pentagon;
+ }
+
+ # is $pentagon in the queue of possible answers?
+ for $sum (sort keys %queue) {
+ ($mm, $nn) = @{$queue{$sum}};
+
+ # this queued number is the sum of 2 pentagons which differ by a pentagon, so ... an answer!
+ if ($sum == $pentagon) { # the queued sum is the pentagon we've just found
+ $diff = $f[$nn] - $f[$mm];
+ $sum2 = $sum + 0; # get rid of zero padding
+ say qq[First method: ];
+ say qq[Pentagon no $mm is $f[$mm]];
+ say qq[Pentagon no $nn is $f[$nn]];
+ say qq[Their sum is $sum2 which is pentagon number $n];
+ say qq[Their difference is $diff which is pentagon number $p{$diff}];
+
+ if (++ $found == $seeking) { # we've achieved the goal
+ say '' . (time - $start) . qq[ seconds\n];
+ return;
+ }
+ delete $queue{$sum};
+
+ # this queued sum of 2 pentagon numbers is less than the one we just found,
+ # so it isn't a pentagon number, so take it out of the queue
+ } elsif ($sum < $pentagon) {
+ delete $queue{$sum};
+
+ # else it's still larger than the pentagon we just found, so leave it in the queue
+ } else {
+ last; # and any others are larger still so we con;t need to look at them yet
+ }
+ }
+ }
+}
+
+sub second_method {
+
+ my ($found, $n, $i, $s, $m, $diff, $sum, %p, @f, $start);
+
+ $found = 0;
+ $start = time;
+
+ for ($n = 1; ; $n ++) {
+
+ # find pentagon numbers sequentially
+ next unless ($i = is_pentagonal($n)); # so $n is the $i'th pentagon
+
+ # so $n is pentagonal
+ $f[$i] = $n; # and the $i'th pentagon number is $f[$i]
+ $p{$n} = $i; # if n is a pentagon, it is the $p{$n}'th one
+ next if $n == 1;
+
+ # check the difference and sum of this pentagon number ($n) and all smaller ones ($m)
+ for $m (1 .. $i - 1) {
+ $diff = $n - $f[$m];
+ $sum = $n + $f[$m];
+
+ # difference is not a pentagon number
+ next unless $p{$diff};
+ next unless $s = is_pentagonal($sum); # sum is not a pentagon number
+
+ # result!
+ say qq[Second method: ];
+ say qq[Pentagon no $i is $n];
+ say qq[Pentagon no $m is $f[$m]];
+ say qq[Their sum is $sum which is pentagon number $s];
+ say qq[Their difference is $diff which is pentagon number $p{$diff}];
+ if (++ $found == $seeking) {
+ say '' . (time - $start) . qq[ secs\n];
+ return;
+ }
+ }
+ }
+}
+
+sub is_pentagonal {
+
+ # per Wikipedia
+
+ my $test = (sqrt(24 * $_[0] + 1) + 1) / 6;
+ my $test1 = $test - int($test + 1e-19);
+ return (abs($test1) <= 1e-19) ? $test : 0;
+}
+