diff options
| author | drbaggy <js5@sanger.ac.uk> | 2021-07-05 11:56:15 +0100 |
|---|---|---|
| committer | drbaggy <js5@sanger.ac.uk> | 2021-07-05 11:56:15 +0100 |
| commit | 44286b8a2788cd6f76fd0f67167d052a86e1782b (patch) | |
| tree | 8e9c2b452fc8475d02057892264d3af29230528c | |
| parent | 7ecb9c24d7c69747e9dee7bd0e6d3a4d25fff466 (diff) | |
| download | perlweeklychallenge-club-44286b8a2788cd6f76fd0f67167d052a86e1782b.tar.gz perlweeklychallenge-club-44286b8a2788cd6f76fd0f67167d052a86e1782b.tar.bz2 perlweeklychallenge-club-44286b8a2788cd6f76fd0f67167d052a86e1782b.zip | |
init
| -rw-r--r-- | challenge-120/james-smith/README.md | 290 |
1 files changed, 4 insertions, 286 deletions
diff --git a/challenge-120/james-smith/README.md b/challenge-120/james-smith/README.md index 11f1a24015..697c8c702a 100644 --- a/challenge-120/james-smith/README.md +++ b/challenge-120/james-smith/README.md @@ -1,4 +1,4 @@ -# Perl Weekly Challenge #119 +# Perl Weekly Challenge #120 You can find more information about this weeks, and previous weeks challenges at: @@ -14,297 +14,15 @@ https://github.com/drbaggy/perlweeklychallenge-club/tree/master/challenge-119/ja # Task 1 - Swap Nibbles -***You are given a positive integer `$N`. Write a script to swap the two nibbles of the binary representation of the given number and print the decimal number of the new binary representation.((( +***You are given a positive integer `$N` less than or equal to `255`. Write a script to swap the odd positioned bit with even positioned bit and print the decimal equivalent of the new binary representation.*** ## The solution -This is a simple binary manipulation problem. +# Task 1 - Clock Angle -We can isolate the lower-nibble by bit-wise `&`ing with `15` or `00001111`. We then move it to the upper-nibble by multiplying by `16` OR using the bit-shift operator `<<` to move the bits 4 spaces to the left. - -We can isolate the upper-nibble by bit-wise `&`ing with `240` or `11110000`, and again use the bit-shift operator `>>` to move 4 bits to the right. BUT actually we don't need to perform the bit-wise `&` IF we assume the number is in range (`0`..`255`). As the `>>` operator just drops the bits shifted off. - -To stitch it back together we just `|` the two numbers together. - -```perl -sub swap_nibble { - return ($_[0]>>4) | (($_[0]&15)<<4); -} -``` - -## Other languages - -I've included some other language versions of this code... - -The first 3 are just direct lifts as Javascript, PHP and Python all have bit shift and bitwise operators! The fourth tho' is a bit of curveball "CESIL" the first -language I was "taught" at school - the code was run from punch cards and the printout was on green bar paper! - -### Javascript -```javascript -TESTS = [ [101,86],[18,33] ]; - -TESTS.forEach(_ => console.log( swap_nibble( _[0] ) == _[1] ? 'OK' : '--' ) ); -TESTS.forEach(_ => console.log( swap_nibble( _[1] ) == _[0] ? 'OK' : '--' ) ); - -function swap_nibble(_) { - return (_>>4) | ((_&15)<<4); -} -``` - -### PHP -```php -<?php - -$TESTS = [ [101,86],[18,33] ]; - -foreach($TESTS as $_ ) { - echo swap_nibble($_[0]) == $_[1] ? 'OK' : '--',"\n"; - echo swap_nibble($_[1]) == $_[0] ? 'OK' : '--',"\n"; -} - -function swap_nibble($_) { - return ($_>>4) | (($_&15)<<4); -} -``` - -### Python -```python -TESTS=[[15,240],[165,90]] - -def swap_nibbles(_): - return (_>>4)|((_&15)<<4) - -for t in TESTS: - print( 'OK' if swap_nibbles(t[0])==t[1] else 'Not OK' ) - print( 'OK' if swap_nibbles(t[1])==t[0] else 'Not OK' ) - -``` -### CESIL - -Thought I'd throw in a slightly curve ball - CESIL "*Computer Education in Schools Instructional Language*" written by ICL was the -first language I was formally taught - back in the days when you had to learn how computers worked at bare bones. So it had a -reduced instruction set with just 14 commands - ADD/SUBTRACT/MULTIPLY/DIVIDE, IN/LOAD/STORE, JUMP/JIZERO/JINEG, PRINT/OUT/LINE & HALT. The layout -of code is "structured" with 8 character indent as it was designed to be stored/run on punch-cards... {The reason the "input" appears after a card -with % in it}. - -CESIL was designed to teach "machine-code" to Computer science students - so other than the "I/O" commands everything else was at a basic operation level. - -**Note:** Interestingly in just 26 lines of code - all 14 of the instructions are used...! - -``` - LINE -Next IN - JINEG End - OUT - PRINT " => " - STORE a - IN - STORE c - LOAD a - DIVIDE 16 - STORE b - MULTIPLY -16 - ADD a - MULTIPLY 16 - ADD b - OUT - PRINT " : " - SUBTRACT c - JIZERO Ok - PRINT "--" - JUMP Line -Ok PRINT "OK" -Line LINE - JUMP Next -End LINE - HALT - % - 240 - 15 - 15 - 240 - 0 - 0 - 255 - 255 - 99 - 54 - -1 -``` -### Side note... an intepreter for CESIL... - -Didn't like the idea of relying on JAVA... so here is a bare bones -interpreter... This uses a dispatch table to execute the commands -- a list of "anonymous" subroutines stored in a hash. - -```perl -use strict; -use warnings; - -## Initialize state -my($MAX,$ptr,$reg,@in,%mem,@code,%ptrs)=(1e6,0,0); - -## Define error messages -my %messages = ( -'i','No further input','d','Division by zero ', -'l','Unknown pointer ','m','Unitialized memory at '); - -## Support functions -sub _e { die sprintf "\n** %s%s [%s @ %d]\n", - $messages{$_[0]},@{$code[$ptr]}[1,0],1+$ptr} -sub _j { exists$ptrs{$_}?($ptr=$ptrs{$_}-1):_e 'l'} -sub _v { /^-?\d+$/?$_:exists$mem{$_}?$mem{$_}:_e 'm'} - -## Command dispatch table -my %commands = ( -'LINE' ,sub{print "\n"}, -'OUT' ,sub{print $reg}, -'PRINT' ,sub{print s/^"//r=~s/"$//r}, -'IN' ,sub{@in?($reg=shift@in):_e 'i'}, -'STORE' ,sub{$mem{$_}=$reg}, -'LOAD' ,sub{$reg=_v}, -'ADD' ,sub{$reg+=_v}, -'SUBTRACT',sub{$reg-=_v}, -'MULTIPLY',sub{$reg*=_v}, -'DIVIDE' ,sub{$_=_v;$reg=$_?int($reg/$_):_e 'd'}, -'JINEG' ,sub{_j if $reg<0}, -'JIZERO' ,sub{_j if !$reg}, -'JUMP' ,sub{_j}, -'HALT' ,sub{exit}, -); - -## Parser loop -while(<>) { - ((@in=map{/^\s+-?\d+\s*$/?0+$_:()}<>),last)if/^ {8}%/; - ($ptrs{$1},$_)=(0+@code,$2) if m/^(\S{1,7})\s+(.*)/; - my($cmd,$data) = split/\s+/,s/^\s+//r=~s/\s+$//r, 2; - die "\n## Unknown command [$cmd @ ",1+@code,"]\n" - unless exists $commands{$cmd}; - push @code, [$cmd,$data//'']; -} -## Execution loop -($commands{$code[$ptr][0]}($_=$code[$ptr][1]),$ptr++) - while--$MAX&& $ptr<@code; -die "\n** Exited without HALT\n"; -``` -# Task 2 - Sequence without 1-on-1 - -***Write a script to generate sequence starting at 1. Consider the increasing -sequence of integers which contain only 1’s, 2’s and 3’s, and do not have any -doublets of 1’s like below. Please accept a positive integer `$N` and print -the `$N`th term in the generated sequence.*** - -``` -1, 2, 3, 12, 13, 21, 22, 23, 31, 32, 33, 121, 122, 123, 131, ... -``` +***You are given time `$T` in the format `hh:mm`. Write a script to find the smaller angle formed by the hands of an analog clock at a given time.*** ## The solution -Note this is a first pass solution.... - -To avoid splitting/unsplitting - we will keep the digits as an array. -We then just increment the last entry of the array, and carry over -any bits to the previous entry. Finally if we get to the start of the -array we unshift a 1 in front of it... - -In the outer loop we compute the next number - but we don't increment -the counter if the number has two adjacent 1s. - -```perl -sub no_11_array { - my( $n, $ptr, @v ) = ( shift, undef, 0 ); - for( my $i = 0; $i < $n; ) { - for( $ptr = $#v; $ptr>-1 && ++$v[$ptr]>3; $ptr--) { - $v[$ptr]=1; - } - unshift @v,1 if $ptr < 0; - $i++ unless "@v"=~m{1 1}; - } - return join q(),@v; -} -``` - -Comparing this to the pure script using numbers and filtering out those -that contain any of the digits `0`, `4` through `9` or the string `11` -using a regexp sees an approximately `15x` speed up. If you optimize -the filter using a series of index calls you can double the speed of the -code at the expense of a slightly longer function BUT it is still less -than a seventh of the performance of the array version. - -```perl -sub no_11_filter { - my $n = shift; - my $v = 0; - while(1){ - return $v unless $n; - $v++; - $n-- if 0 > index( $v,'4' ) && 0 > index( $v,'0' ) - && 0 > index( $v,'5' ) && 0 > index( $v,'6' ) - && 0 > index( $v,'7' ) && 0 > index( $v,'8' ) - && 0 > index( $v,'9' ) && 0 > index( $v,'11' ); - } -} - -``` - -### Making the code more *readable*.... - -This is a nice challenge to make the code more "readable", by encapsulating our new "number" string as an object. - -We will overload it's increment operator so that we can generate the sequence - -``` - 1, 2, 3, 11, 12, 13, 21, 22, 23, 31, 32, 33, 111 -``` - -The object itself is just a blessed array. - -We use `overload` to define both the "increment" operator `++` and the "stringfy" -operator "". - -We then define a simple method to test whether or not the "string" contains a double 1. - -The package code is below - but first the loop in the code: - -```perl -sub no_11_object { - my( $n, $v ) = ( shift, Three->new ); - for( my $i = 0; $i < $n; $v++, $i++ ) { - $v++ while $v->has_double_one; - } - return "$v"; -} -``` - -Although the code is "cleaner" performance is affected - it is about half the speed of the optimal (array) solution but still 4 times as fast as the (non-regex) filter method. -```perl -package Three; - -use overload '++' => 'incr'; -use overload '""' => 'str'; - -sub new { - return bless [], 'Three'; -} - -sub has_double_one { - my( $f, @v ) = @{$_[0]}; - ( $f == 1 && $v[0] == 1 ) ? ( return 1 ) : ( $f = shift @v ) while @v; - return 0; -} - -sub incr { - my($v,$ptr) = (shift,-1); - for( $ptr = $#$v; $ptr>-1 && ++$v->[$ptr]>3; $ptr--) { - $v->[$ptr]=1; - } - unshift @{$v}, 1 if $ptr < 0; -} - -sub str { - return join '',@{$_[0]}; -} -1; -``` |
