I’ve been doing the Weekly
Challenges. The
latest
involved date calculations and Roman mathematics. (Note that this ends
today.)
Task 1: Friday 13th
You are given a year number in the range 1753 to 9999.
Write a script to find out how many dates in the year are Friday
13th, assume that the current Gregorian calendar applies.
Obviously one could build this with a lookup table (there are only
fourteen year-weekday patterns, seven for the days that 1 January can
fall on multiplied by two for whether it's a leap year or not), but it
seemed more straightforward just to do the calculations directly. So
for the 13th of each month, we find the day of the week, and increment
the counter if it's a Friday.
For Lua I wrote some basic date calculation functions (borrowing code
from my existing PostScript library). For Rust I used chrono
.
Everything else had enough ability to do these calculations built in
to the core.
For example, with Perl. If I were doing this as part of a real project
I'd use one of the other date libraries, but this is already
available, so…
use Time::Local qw(timegm_modern);
sub friday13th($y) {
my $f = 0;
foreach my $m (1 .. 12) {
if ((gmtime(timegm_modern(0, 0, 12, 13, $m - 1, $y)))[6] == 5 ) {
$f++;
}
}
return $f;
}
Task 2: Roman Maths
Write a script to handle a 2-term arithmetic operation expressed in
Roman numerals.
I borrowed the basic to- and from-Roman algorithms from the Rosetta
Code example
for Rust, then reimplemented them in the other languages. (I ended up
writing a bunch of tests for these converters, to use as I was writing
them, along with the supplied tests for the maths engine.) All it
needs is string slicing and a search from beginning of string.
Then romanmaths
itself just has to split the input string, check the
operator, and apply range checks to generate the non-Roman-numeral
output.
Kotlin and Rust don't have integer exponentiation built in (only
floating point), but it's easy enough to write.
Kotlin:
fun roman2int(roman: String): Int {
If we have a null string, bail out early with zero.
if (roman == "") {
return 0
}
Using all possible single-element matches:
for (s in listOf(
Pair( "M", 1000 ),
Pair( "CM", 900 ),
Pair( "D", 500 ),
Pair( "CD", 400 ),
Pair( "C", 100 ),
Pair( "XC", 90 ),
Pair( "L", 50 ),
Pair( "XL", 40 ),
Pair( "X", 10 ),
Pair( "IX", 9 ),
Pair( "V", 5 ),
Pair( "IV", 4 ),
Pair( "I", 1 ),
)) {
If the string starts with the match symbol
if (roman.startsWith(s.first)) {
Add the match value, and run roman2int
again on what's left of the string.
return s.second + roman2int(roman.slice(s.first.length .. roman.length-1))
}
}
return 0
}
Going the other way is rather simpler.
fun int2roman(n0: Int): String {
var number = n0
var mn = ""
Highest to lowest value:
for (s in listOf(
Pair( "M", 1000 ),
Pair( "CM", 900 ),
Pair( "D", 500 ),
Pair( "CD", 400 ),
Pair( "C", 100 ),
Pair( "XC", 90 ),
Pair( "L", 50 ),
Pair( "XL", 40 ),
Pair( "X", 10 ),
Pair( "IX", 9 ),
Pair( "V", 5 ),
Pair( "IV", 4 ),
Pair( "I", 1 ),
)) {
If the number is higher than or equal to the value, subtract value
from number and append string representation.
while (s.second <= number) {
mn += s.first
number -= s.second
}
}
return mn
}
Moderately efficient integer exponentiation. Treat the exponent as a
bitmask, squaring the input at each step and multiplying it into the
output if the mask matches.
fun intPow(x0: Int,pow0: Int): Int {
var x=x0
var pow=pow0
var ret=1
while (pow > 0) {
if ( (pow and 1) == 1 ) {
ret *= x
}
x *= x
pow = pow shr 1
}
return ret
}
The expression evaluator. No need for complicated parsing, just split
the string into three.
fun romanmaths(ax: String): String {
val elems = ax.split(" ")
val a = roman2int(elems[0])
val b = roman2int(elems[2])
Set output value based on operator. (It's explicitly the case that a
division with remainder produces an error, which I represent as -1.)
var c = -1
if (elems[1] == "+") {
c = a + b
} else if (elems[1] == "-") {
c = a - b
} else if (elems[1] == "*") {
c = a * b
} else if (elems[1] == "/") {
if (a % b == 0) {
c = a / b
}
} else if (elems[1] == "**") {
c = intPow(a, b)
}
Return the result.
if (c > 3999 || c < 0) {
return "non potest"
} else if (c == 0) {
return "nulla"
} else {
return int2roman(c)
}
}
Full code on
github.
Comments on this post are now closed. If you have particular grounds for adding a late comment, comment on a more recent post quoting the URL of this one.