Counting from 100 is the Fun Part
TASK #1 - Roman Calculator
Write a script that accepts two roman numbers and operation. It should then perform the operation on the give roman numbers and print the result.
For example,
perl ch-1.pl V + VI
It should print
XI
The hard part of this is handling Roman numerals. Unless, of course, you use the pre-existing library to handle this, such as Roman.
Roman provides three important functions:
isroman
, which tells us if the string given is a valid Roman numeralroman
, which converts a number from Arabic numerals to Romanarabic
, which converts a Roman numeral to Arabic from Roman
So, the process is:
- convert the numbers to Arabic
- do the math
- convert the result to Roman
I could’ve used eval
, but I always worry using it, even when I have full control over what gets added. So, I use a hash to test if the mathematical operators are ones I want to support.
I’m half-considering making a dispatch table, whcih would allow me to turn those four if statements into $a3 = $dispatch->{$op}($a1,$a2)
, but not tonight.
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use feature qw{ postderef say signatures state switch };
no warnings
qw{ experimental::postderef experimental::smartmatch experimental::signatures };
use Roman;
my %operators = map { $_ => 1 } qw{ + - / * };
if ( scalar @ARGV > 2 ) {
my ( $r1, $op, $r2 ) = @ARGV;
if ( !$operators{$op} ) {
say 'not an operator';
exit;
}
if ( !isroman($r1) ) { say qq{"$r1" is not a roman numeral}; exit; }
if ( !isroman($r2) ) { say qq{"$r2" is not a roman numeral}; exit; }
my $a1 = arabic($r1);
my $a2 = arabic($r2);
my $a3 = 0;
if ( $op eq '+' ) { $a3 = $a1 + $a2 }
if ( $op eq '-' ) { $a3 = $a1 - $a2 }
if ( $op eq '*' ) { $a3 = $a1 * $a2 }
if ( $op eq '/' ) { $a3 = $a1 / $a2 }
my $r3 = uc roman($a3);
say qq{ $r1 $op $r2 = $r3 };
}
else { say 'We need an operator and two roman numbers' }
TASK #2 - Gapful Number
Write a script to print first 20 Gapful Numbers greater than or equal to 100.
A Gapful Number is one such that, if you make a two-digit number from the first and last digit of the number, that number divides evenly into the number in question.
So, we can either turn 100
into [1,0,0]
and grab the first and last entry in the array, or we can do the same with substr($var,0,1)
and substr($var,-1)
. We can then do $v1 *10 + $v2
, or just join '', $v1, $v2
, which is what I did.
The fun part, to me, is coming up with the numbers in range. Looking at the suggested page explaining Gapful Numbers, we know that the first 20 meaningful Gapful Numbers (numbers <100 are trivially gapful, because, for example, 99%99=0
) and know that they exist beneath 200, but I don’t really know that until the code proves it. Some alteratives include:
for ( my $i = 100; $i< 1000 ; $i++) { ... }
for my $i ( 100 .. 1000) { ... }
while ( @matches < 20 ) { $i++; ... }
map { ... } 100 .. 1000
- An Iterator, or Lazy List
I don’t really do anything with that first style of for
loop, because that’s one of the first things you learn in programming. C-style for loops are useful and good, but I used the ranged version in languages where that’s possible.
That last one may be a new one for you. We want a function that starts where we want it to start and goes up by one each time called.
sub make_iterator ( $n ) {
return sub {
state $i = $n ;
return $i++
}
}
This gives us a function that returns a function. This function, like $iter = make_iter(100)
, gives us the $iter
variable, which is a function to be called like $iter->()
, which each time returns the next biggest number.
I used the term Lazy Loading, and it comes in here because, when we use 100..1000
, we create a 900-bucket anonymous array, while using an iterator will keep it from using that memory. In this smaller case, this will not be a great issue, but if your data grows larger, it may become a problem.
My variations on the theme:
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use feature qw{ postderef say signatures state switch };
no warnings qw{ experimental::postderef experimental::smartmatch experimental::signatures };
# Write a script to print first 20 Gapful Numbers
# greater than or equal to 100.
# a Gapful Number is a number where
# a second number is formed by the first and last digit of the number
# which is a factor in that number.
# for example, 100 forms 10 and 100/10 = 10
# while 101 forms 11 and 101/11 = 9.181818...
# When I'm trying to be readable,
# I declare an array to push things into
# I use a while loop based on the size of that array
# I iterate through numbers with ++
# I split the number into an array with the easiest regex
# and I join with an empty string
# I push to the array if $n % $i == 0
my @x;
my $n = 100;
while ( scalar @x < 20 ) {
my @n = split //, $n;
my $i = join '', $n[0], $n[-1];
push @x, $n if $n % $i == 0;
$n++;
}
say join "\n", @x;
say '-' x 30;
# When I'm trying to show off a little more
# I use a for loop ending at an abstractly large point
# I name the for loop
# I use state so the count variable only exists within the loop
# I use the named last to break the loop
LOOP: for my $n ( 100 .. 1000 ) {
state $c = 0;
my @n = split //, $n;
my $i = join '', $n[0], $n[-1];
if ( 0 == $n % $i ) {
$c++;
say $n;
}
last LOOP if $c >= 20;
}
say '-' x 30;
# When I'm trying to be way-cool functional dev
# I start with a range of 100..1000
# I use join to stringify the output
# I use a grep and a state variable to limit to 20
# I use substr to pull the first and last digit from the number
# and only create one variable in the second grep
say join "\n", grep { state $c = 0; $c++ < 20 }
grep { my $i = join '', substr( $_, 0, 1 ), substr( $_, -1 ); $_ % $i == 0 }100 .. 1000;
say '-' x 30;
my $next = make_iterator(100);
while ( my $n = $next->() ) {
state $c = 0;
my $i = my $i = join '', substr( $n, 0, 1 ), substr( $n, -1 );
if ( 0==$n%$i){
say $n;
$c++;
}
last if $c >19;
}
sub make_iterator ( $start ) {
return sub {
state $i = $start;
return $i++;
}
}
This problem doesn’t naturally lend itself to recursion, but I might have to make a recursive version, just for giggles.