aboutsummaryrefslogtreecommitdiff
path: root/challenge-179
diff options
context:
space:
mode:
authordrbaggy <js5@sanger.ac.uk>2022-08-26 23:12:07 +0100
committerdrbaggy <js5@sanger.ac.uk>2022-08-26 23:12:07 +0100
commit4b79733f34ec262f0394f31e0e99f0dbb22442fe (patch)
tree5c32030651b3a37f52e3163d54d461b90826ada5 /challenge-179
parent307c8dd5ab60c2c8f4dc9d84dfd87999fd475edf (diff)
downloadperlweeklychallenge-club-4b79733f34ec262f0394f31e0e99f0dbb22442fe.tar.gz
perlweeklychallenge-club-4b79733f34ec262f0394f31e0e99f0dbb22442fe.tar.bz2
perlweeklychallenge-club-4b79733f34ec262f0394f31e0e99f0dbb22442fe.zip
updated readme
Diffstat (limited to 'challenge-179')
-rw-r--r--challenge-179/james-smith/README.md190
1 files changed, 101 insertions, 89 deletions
diff --git a/challenge-179/james-smith/README.md b/challenge-179/james-smith/README.md
index 7ddb0cf9ce..7ddde258cb 100644
--- a/challenge-179/james-smith/README.md
+++ b/challenge-179/james-smith/README.md
@@ -1,7 +1,7 @@
-[< Previous 176](https://github.com/drbaggy/perlweeklychallenge-club/tree/master/challenge-176/james-smith) |
-[Next 178 >](https://github.com/drbaggy/perlweeklychallenge-club/tree/master/challenge-178/james-smith)
+[< Previous 178](https://github.com/drbaggy/perlweeklychallenge-club/tree/master/challenge-178/james-smith) |
+[Next 180 >](https://github.com/drbaggy/perlweeklychallenge-club/tree/master/challenge-180/james-smith)
-# The Weekly Challenge 177
+# The Weekly Challenge 179
You can find more information about this weeks, and previous weeks challenges at:
@@ -13,120 +13,132 @@ 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-177/james-smith
+https://github.com/drbaggy/perlweeklychallenge-club/tree/master/challenge-179/james-smith
-# Task 1 - Damm Algorithm
+# Task 1 - Ordinal Number Spelling
-***You are given a positive number, `$n`. Write a script to validate the given number against the included check digit.***
-
-The wikipedia page outlines the algoritm: https://en.wikipedia.org/wiki/Damm_algorithm
-
-## Algorithm
-
-We have a 10x10 lookup table - each row contains the numbers 0..9 permuted.
-
-We break the numbers up in to digits, and loop through them one at a time computing the subsequent check digit. We start with a check digit of `$d=0`.
-For each digit `$n` we take the value of the `$d` row and `$n` column and assign this to `$d`.
-
-If we are checking a contained check digit then at the end of the loop the value should be `0`. If not then we have just computed the check digit.
+***You are given a positive number, `$n`. Write a script to spell the ordinal number.***
## Solution
-```perl
-const my @M => (
- [0,3,1,7,5,9,8,6,4,2],
- [7,0,9,2,1,5,4,8,6,3],
- [4,2,0,6,8,7,1,3,5,9],
- [1,7,5,0,9,8,3,4,2,6],
- [6,1,2,3,0,4,5,9,7,8],
- [3,6,7,4,2,0,9,5,8,1],
- [5,8,6,9,7,2,0,1,3,4],
- [8,9,4,5,3,6,2,0,1,7],
- [9,4,3,8,6,1,7,2,0,5],
- [2,5,8,1,4,3,6,7,9,0],
-);
-
-sub check_damm { my $i=0; $i=$M[$i][$_] for split//,pop; $i?0:1; }
-sub damm_digit { my $i=0; $i=$M[$i][$_] for split//,pop; $i; }
-```
+To support `$n` greater than 999 we have to split the number into chunks - much like the classic
+`commify` function does.
-# Task 2 - Palindromic Prime Cyclops
+We use a regex to split into 3 number chunks (but we reverse the number and resultant strings) so
+the regex returns a list where the first chunk is either 1, 2 or 3 digits long and the rest are
+3 digits long.
-***Write a script to generate palindromic cyclops primes***
+To get the ordinal of any number we look for the last chunk that is not `000`.
- * Palindromic - reads the same forward and back
- * Cyclops - odd number of digits - central digit is 0
- * Prime - only factors are 1 and itself.
+If this is the first one we need to start by computing the ordinal of that block. If not we
+ignore it and find the natural number of the block and add thousandth, millionth etc.
-## Solution
+For values > 999, we just stitch together the numbers for each block.
-There are two approaches:
+We have 6 list of number words:
- * Generate a list of palindromic cyclops numbers, check if any are prime
- * Generate a list of prime numbers, and check if any are palindromic cyclops numbers.
+ * 1-19
+ * 20, 30, ..., 90
+ * thousand, million, billion, ... novemnonagintillion - powers of 1000...
-But which one is best. Now say we have to generate the list of these numbers up 1 billion. So these are numbers of the form:
+and then the same as ordinals...
+```perl
+my @ord = qw(x first second third fourth fifth sixth seventh eighth ninth tenth eleventh twelfth thirteenth
+ forteenth fifteenth sixteenth seventeenth eighteeinth nineteenth);
+my @ord_10 = qw(z tenth twentieth thirtieth fortieth fiftieth sixtieth seventiet eightieth ninetieth hundredth);
+my @power = map { [$_,$_.'th'] } qw(x thousand), map { $_.'illion' } qw(m b tr quad quin sext sept oct nov),
+ ( map { $a=$_, map { $_.$a } qw(un duo tre quattuor quin sex sept octo novem) }
+ qw(dec vigint trigint quardagint quinquagint sexagint septuagint octagint nonagint) );
+my @nat = qw(a one two three four five six seven eight nine ten eleven twelve thirteen forteen fifteen
+ sixteen seventeen eighteen nineteen);
+my @nat_10 = qw(b ten twenty thirty fourty fifty sixty seventy eighty ninety);
```
- a b c, d 0 d, c b a
-```
-
-So if we generate the list of palindromic cyclops numbers we just need to generate all numbers from `1..9,999`, if we generate all primes in this range there are `50,847,534`, this is `5,085x` factor more.
+with these phrases we can create all ordinals.
-Which suggest the first algorithm will be faster - to make this generic we change include a variable count `$K` for the number of entries to return.
+We have 2 support functions - `_natural` which gets the text version of a natural number between 1
+and 999; and `_ordinal` which gets the text version of the an ordinal number between 1st and 999th.
```perl
-my($i,$t)=0;
+sub ordinal {
+ my ($ptr,$last,@parts,@result) = (0, map { scalar reverse } ((reverse pop) =~ m{(\d{1,3})}g));
+ @result = _ordinal($last, !@parts ) if -$last;
+ $ptr++, -$_ && (unshift @result, _natural($_).' '.$power[$ptr][ @result ? 0 : 1 ] ) for @parts;
+ "@result" =~ s/\s+/ /gr
+}
+
+## Create a natural triple of 1s, 10, 100s
+sub _natural {
+ my $v = pop;
+ join ' and ',
+ ($v > 99) ? $nat[$v/100].'-hundred' : (), ## Hundred part
+ $v%100 ? ( $v%100 > 19 ? $nat_10[($v%100)/10].( $v%10 ? '-'.$nat[$v%10] : '' ) ## 20+ tens-units
+ : $v%100 > 0 ? $nat[$v%100] : () ) : () ## "units"
+}
-for(1..$K) {
- ($++$i)!~/0/ && is_prime( $t = $i.'0'.reverse$i ) ? say $t : redo;
+## Create an ordinal triple
+sub _ordinal {
+ my($v,$flag) = @_;
+
+ join ' and ',
+ (!$flag && $v < 100) ? '' : (),
+ ($v > 99) ? $nat[$v/100].($v%100?'-hundred':'-hundredth') : (),
+ $v%100 ? ( $v%100 > 19 ? ( $v%10 ? $nat_10[($v%100)/10].'-'.$ord[$v%10] : $ord_10[($v%100)/10] )
+ : $v%100 > 0 ? $ord[$v%100] : () ) : ()
}
```
-In fact this is efficient enough to get the first 1,000,000 such numbers back in slightly under 10 seconds. The millionth one being the 17 digit number: `76,276,363,036,367,267`.
+# Task 2 - Unicode Sparkline
-But we can get better performance as we are currently scanning all numbers which start with 2,4,5,6,8 which we know are not prime {first digit -> last digit!} - this leads us to a slightly more complex but faster piece of code:
+***You are given a list of positive numbers, `@n`. Write a script to print sparkline in Unicode for the given list of numbers.***
-Reducing to numbers that start in 1,3,5,7 reduces the search space by a factor of 4/9.
+## Note
-By removing those that line between say 100... and 111... which must contain at least one zero further reduces this by 8/9 - giving us a reduction of 32/81 numbers or roughly 39.5%.
+There is no "clear-unique" definition of a sparkline - it can take many forms - as we are using unicode we will stick with the vertical line design.
-```perl
-my( $magnitude, $ones, $start, $count, $result ) =
- ( 1, 0, time, $ARGV[0]//20, '-' );
-O: while(1) {
- for my $first (1,3,7,9) {
- !/0/ && is_prime( $_ .= '0' . reverse $_ ) &&
- say && ( --$count || ( $result = $_ ) && last O )
- for $first * $magnitude + $ones .. ( $first + 1 ) * $magnitude - 1;
- }
- $magnitude *= 10;
- $ones *= 10;
- $ones++;
-}
-warn time-$start, "\t", $result, "\n";
-```
+## Solution
-Where we scan from 111.. to 199.., 311.. to 399.., 711.. to 799.., 911.. to 999...
+For any row - it consists of a series of points and a series of gaps between them.
+Between a point at position `$n` and `$m` the gap is size `$m-$n-1`. We then use
+a loop over all the points to generate the line..
-With this new version of the code we get this 1,000,000 entry coming back in around 10 seconds. Running a bit longer gives the 10,000,000th entry in a little over 2 minutes.
+As we are using nested maps, we occassionally have entries (*e.g.* updating `$k` where
+we need to hide the value we created - you can use the array version of multiply (`x`)
+for this - by multiplying by `0`, e.g. `($k=$_)x 0`....
-| $K | Time v1 | Time v2 | $Kth value |
-| ----------: | ---------: | ---------: | ------------------------: |
-| 1 | 0.000018 | 0.000018 | 101 |
-| 10 | 0.000056 | 0.000039 | 1,120,211 |
-| 100 | 0.000498 | 0.000309 | 146,505,641 |
-| 1,000 | 0.006829 | 0.004846 | 19,178,087,191 |
-| 10,000 | 0.114116 | 0.064679 | 3,446,840,486,443 |
-| 100,000 | 1.286905 | 0.787072 | 387,695,909,596,783 |
-| 1,000,000 | 23.104720 | 9.993216 | 76,276,363,036,367,267 |
-| 10,000,000 | 250.055394 | 124.405436 | 9,523,518,610,168,153,259 |
+If we wished to limit the vertical height we could apply scaling to limit to 2 or 3 lines.
-Flipping to use `Math::Perl::Util::GMP` and `is_provable_prime` we can get a "guess" at even longer entries. "Guess" because the `is_provable_prime` can return NO, YES or probably, as there is compromise between accuracy and performance when it comes to checking these large primes. So for 100 million and 1 billion we get the following
-answers and timings. The latter being a smidge under 21 hours.
+```perl
-| $K | Time v1 | Time v2 | $Kth value | Time |
-| ------------: | ---------: | ---------: | --------------------------------: | ------------------: |
-| 100,000,000 | - | 6370.062 | 11,459,314,276,067,241,395,411 | 1 hr 46 min 10 sec |
-| 1,000,000,000 | - | 75584.858 | 1,255,854,925,630,365,294,585,521 | 20 hr 59 min 45 sec |
+binmode STDOUT, ':utf8'; ## Set output to UTF
+
+const my $LINE => "\x{2500}"; ## Horizontal line
+const my $START => "\x{2534}"; ## Inverted T
+const my $FULL => "\x{2577}"; ## Full-height line
+const my $HALF => "\x{2502}"; ## Half-height line
+
+sub spark_line {
+ my($mx,$k,$l,%x)=0
+ $x{$_}++ for @{$_}; ## Create an array of values with counts
+ ($_>$mx) && ($mx=$_) for values %x; ## Find max count..
+ ## We could apply scaling here to keep sparkline
+ ## a max-height but results would not be as tood
+
+ ## We layout the sparks over multiple lines - to
+ ## accodomate height {see note on scaling to keep
+ ## height to a given value}
+ ## If mx > 1 we need to use multiple lines - foreach
+ ## of these we either have a full height "|" or
+ ## a half-height line "|" or nothing " "...
+ map( {
+ ($l,$k) = ($_<<1,-1); join '',
+ map { ' ' x ($_-$k-1).( $x{$_}<$l ? ' ' : $x{$_} == $l ? $FULL : $HALF ), ($k=$_)x 0 }
+ sort { $a <=> $b }
+ keys %x
+ }
+ reverse 1 .. $mx / 2
+ ),
+ ($k=-1)x 0,join( '', map { $LINE x ($_-$k-1) . $START, ($k=$_)x 0 } sort {$a<=>$b} keys %x )
+}
+```