Saturday, March 29, 2008

Finding your Star Wars name

It's high time we did something fun. We're going to write a function that determines a person's Star Wars name.


ASP

  1. function starwarsname(firstName, lastName, maidenName, city)
  2.     starwarsname = Left(firstName, 3) & LCase(Left(lastName, 2)) & " " & Left(maidenName, 2) & LCase(Left(city, 3))
  3. end function

PHP

  1. function starwarsname($firstName, $lastName, $maidenName, $city)
  2. {
  3.     return substr($firstName, 0, 3) . strtolower(substr($lastName, 0, 2)) . " " . substr($maidenName, 0, 2) . strtolower(substr($city, 0, 3));
  4. }

Saturday, March 22, 2008

Calculating Easter

It's Easter weekend, and as promised we're going to write a function to calculate when Easter occurs for a given year. Easter is a strange holiday in that it jumps around the calendar between March and April. This is because it is tied to the lunar cycle. Easter occurs on the first Sunday after the Paschal full moon.


ASP

  1. function dateEaster(someYear)
  2.     Dim goldenNumber
  3.     Dim solarCorrection
  4.     Dim lunarCorrection
  5.     Dim paschalFullMoon
  6.     Dim dominicalNumber
  7.     Dim difference
  8.     Dim dayEaster
  9.     goldenNumber = (someYear Mod 19) + 1
  10.     if someYear <= 1752 then
  11.         ' Julian calendar
  12.         dominicalNumber = (someYear + (someYear / 4) + 5) Mod 7
  13.         paschalFullMoon = (3 - (11 * goldenNumber) - 7) Mod 30
  14.     else
  15.         ' Gregorian calendar
  16.         dominicalNumber = (someYear + (someYear / 4) - (someYear / 100) + (someYear / 400)) Mod 7
  17.         solarCorrection = (someYear - 1600) / 100 - (someYear - 1600) / 400
  18.         lunarCorrection = (((someYear - 1400) / 100) * 8) / 25
  19.         paschalFullMoon = (3 - 11 * goldenNumber + solarCorrection - lunarCorrection) Mod 30
  20.     end if
  21.     do until dominicalNumber > 0
  22.         dominicalNumber = dominicalNumber + 7
  23.     loop
  24.     do until paschalFullMoon > 0
  25.         paschalFullMoon = paschalFullMoon + 30
  26.     loop
  27.     if paschalFullMoon = 29 or (paschalFullMoon = 28 and goldenNumber > 11) then
  28.         paschalFullMoon = paschalFullMoon - 1
  29.     end if
  30.     difference = (4 - paschalFullMoon - dominicalNumber) Mod 7
  31.     if difference < 0 then
  32.         difference = difference + 7
  33.     end if
  34.     dayEaster = paschalFullMoon + difference + 1
  35.     if dayEaster < 11 then
  36.         ' Easter occurs in March.
  37.         dateEaster = DateSerial(someYear, 3, dayEaster + 21)
  38.     else
  39.         ' Easter occurs in April.
  40.         dateEaster = DateSerial(someYear, 4, dayEaster - 10)
  41.     end if
  42. end function

PHP

  1. function dateEaster($someYear)
  2. {
  3.     $goldenNumber = fmod($someYear, 19) + 1;
  4.     if ($someYear <= 1752)
  5.     {
  6.         // Julian calendar
  7.         $dominicalNumber = fmod($someYear + ($someYear / 4) + 5, 7);
  8.         $paschalFullMoon = fmod(3 - (11 * $goldenNumber) - 7, 30);
  9.     }
  10.     else
  11.     {
  12.         // Gregorian calendar
  13.         $dominicalNumber = fmod($someYear + ($someYear / 4) - ($someYear / 100) + ($someYear / 400), 7);
  14.         $solarCorrection = ($someYear - 1600) / 100 - ($someYear - 1600) / 400;
  15.         $lunarCorrection = ((($someYear - 1400) / 100) * 8) / 25;
  16.         $paschalFullMoon = fmod(3 - 11 * $goldenNumber + $solarCorrection - $lunarCorrection, 30);
  17.     }
  18.     while ($dominicalNumber < 0)
  19.     {
  20.         $dominicalNumber += 7;
  21.     }
  22.     while ($paschalFullMoon < 0)
  23.     {
  24.         $paschalFullMoon += 30;
  25.     }
  26.     if ($paschalFullMoon == 29 || ($paschalFullMoon == 28 && $goldenNumber > 11))
  27.     {
  28.         $paschalFullMoon--;
  29.     }
  30.     $difference = fmod(4 - $paschalFullMoon - $dominicalNumber, 7);
  31.     if ($difference < 0)
  32.     {
  33.         $difference += 7;
  34.     }
  35.     $dayEaster = $paschalFullMoon + $difference + 1;
  36.     if ($dayEaster < 11)
  37.     {
  38.         // Easter occurs in March.
  39.         $dateEaster = mktime(0, 0, 0, 3, $dayEaster + 21, $someYear);
  40.     }
  41.     else
  42.     {
  43.         // Easter occurs in April.
  44.         $dateEaster = mktime(0, 0, 0, 4, $dayEaster - 10, $someYear);
  45.     }
  46.     return $dateEaster;
  47. }

We can calculate the other ecclesiastical holidays by offsetting the date of Easter with the number of days between Easter and the holiday we're looking for.


ASP

  1. function dateGoodFriday(someYear)
  2.     dateGoodFriday = DateAdd("d", -2, dateEaster(someYear))
  3. end function
  4. function datePalmSunday(someYear)
  5.     datePalmSunday = DateAdd("d", -7, dateEaster(someYear))
  6. end function
  7. function dateAshWednesday(someYear)
  8.     dateAshWednesday = DateAdd("d", -46, dateEaster(someYear))
  9. end function
  10. function dateAscensionDay(someYear)
  11.     dateAscensionDay = DateAdd("d", 39, dateEaster(someYear))
  12. end function
  13. function datePentecost(someYear)
  14.     datePentecost = DateAdd("d", 49, dateEaster(someYear))
  15. end function

PHP

  1. function dateGoodFriday($someYear)
  2. {
  3.     $easter = getDate(dateEaster($someYear));
  4.     return mktime(0, 0, 0, $easter[mon], $easter[mday] - 2, $easter[year]);
  5. }
  6. function datePalmSunday($someYear)
  7. {
  8.     $easter = getDate(dateEaster($someYear));
  9.     return mktime(0, 0, 0, $easter[mon], $easter[mday] - 7, $easter[year]);
  10. }
  11. function dateAshWednesday($someYear)
  12. {
  13.     $easter = getDate(dateEaster($someYear));
  14.     return mktime(0, 0, 0, $easter[mon], $easter[mday] - 46, $easter[year]);
  15. }
  16. function dateAscensionDay($someYear)
  17. {
  18.     $easter = getDate(dateEaster($someYear));
  19.     return mktime(0, 0, 0, $easter[mon], $easter[mday] + 39, $easter[year]);
  20. }
  21. function datePentecost($someYear)
  22. {
  23.     $easter = getDate(dateEaster($someYear));
  24.     return mktime(0, 0, 0, $easter[mon], $easter[mday] + 49, $easter[year]);
  25. }

Saturday, March 15, 2008

Roman Numerals, Part 2

Last week we converted integers into Roman numerals, and I promised this week we would write a function to do the opposite. Part of the difficulty in working with Roman numerals is the subtractive notation. The number four can be written as IV or IIII; writing it as IV is the subtractive notation. When I precedes V, it means one less than five; if you reverse the two (VI), it means one more than five.


To make our life easier, we want to expand the subtractive notation so that each numeral is equal to its value. While we're at it, we'll also write a function to compress Roman numerals back in subtractive notation.


ASP

  1. function roman_expand(ByVal roman)
  2.     roman = replace(roman, "CM", "DCCCC")
  3.     roman = replace(roman, "CD", "CCCC")
  4.     roman = replace(roman, "XC", "LXXXX")
  5.     roman = replace(roman, "XL", "XXXX")
  6.     roman = replace(roman, "IX", "VIIII")
  7.     roman = replace(roman, "IV", "IIII")
  8.     roman_expand = roman
  9. end function
  10. function roman_compress(ByVal roman)
  11.     roman = replace(roman, "DCCCC", "CM")
  12.     roman = replace(roman, "CCCC", "CD")
  13.     roman = replace(roman, "LXXXX", "XC")
  14.     roman = replace(roman, "XXXX", "XL")
  15.     roman = replace(roman, "VIIII", "IX")
  16.     roman = replace(roman, "IIII", "IV")
  17.     roman_compress = roman
  18. end function

PHP

  1. function roman_expand($roman)
  2. {
  3.     $roman = str_replace("CM", "DCCCC", $roman);
  4.     $roman = str_replace("CD", "CCCC", $roman);
  5.     $roman = str_replace("XC", "LXXXX", $roman);
  6.     $roman = str_replace("XL", "XXXX", $roman);
  7.     $roman = str_replace("IX", "VIIII", $roman);
  8.     $roman = str_replace("IV", "IIII", $roman);
  9.     return $roman;
  10. }
  11. function roman_compress($roman)
  12. {
  13.     $roman = str_replace("DCCCC", "CM", $roman);
  14.     $roman = str_replace("CCCC", "CD", $roman);
  15.     $roman = str_replace("LXXXX", "XC", $roman);
  16.     $roman = str_replace("XXXX", "XL", $roman);
  17.     $roman = str_replace("VIIII", "IX", $roman);
  18.     $roman = str_replace("IIII", "IV", $roman);
  19.     return $roman;
  20. }

These expand and compress functions would be essential if you wanted to write some functions to actually perform arithmetic on Roman numerals, and although on paper Roman addition is fairly straightforward, an implementation of it in code performs more poorly than simply converting the Roman numeral back into an integer and using the built-in math operators.


The following ASP function requires the InstrCount function from my previous post.


ASP

  1. function arabic(ByVal roman)
  2.     Dim result
  3.     result = 0
  4.     ' Remove subtractive notation.
  5.     roman = roman_expand(roman)
  6.     ' Calculate for each numeral.
  7.     result = result + InstrCount(roman, "M") * 1000
  8.     result = result + InstrCount(roman, "D") * 500
  9.     result = result + InstrCount(roman, "C") * 100
  10.     result = result + InstrCount(roman, "L") * 50
  11.     result = result + InstrCount(roman, "X") * 10
  12.     result = result + InstrCount(roman, "V") * 5
  13.     result = result + InstrCount(roman, "I")
  14. end function

PHP

  1. function arabic($roman)
  2. {
  3.     $result = 0;
  4.     // Remove subtractive notation.
  5.     $roman = roman_expand($roman);
  6.     // Calculate for each numeral.
  7.     $result += substr_count($roman, 'M') * 1000;
  8.     $result += substr_count($roman, 'D') * 500;
  9.     $result += substr_count($roman, 'C') * 100;
  10.     $result += substr_count($roman, 'L') * 50;
  11.     $result += substr_count($roman, 'X') * 10;
  12.     $result += substr_count($roman, 'V') * 5;
  13.     $result += substr_count($roman, 'I');
  14.     return $result;
  15. }

Although I don't have code for you to actually add two Roman numerals together, I will tell you how to do it. For this example, we will add the numerals XIV and VII. First we must remove the subtractive notation, so XIV becomes XIIII and VII stays as it is. On paper we would then concatenate the two together and sort them.


XIIII + VII = XIIIIVII = XVIIIIII


We already have a concatenation operator, but we would need to write a custom sorting function for Roman numerals. If we're going to go that much trouble, it might be better to just build a new string by counting the number of each numeral in the two strings we are given. This would allow us to handle the situation above where we now have six I's. Our next step is to combine similar numerals that are equal to the next larger numeral.


XVIIIIII = XVVI = XXI


In code, it would be best to start with the smallest numeral, I, and work our way towards the largest numeral.


Subtraction of Roman numerals involves expanding and cancelling until there is only one number left.


XIV - VI = XIIII - VI = XIII - V = VVIII - V = VIII


I leave it up to you to research how to perform multiplication and division of Roman numerals, and whether or not you want to write functions for Roman arithmetic. Such functions would not be useful in production code, but writing them would be a good exercise, and it would be interesting to compare the runtimes with conversion back to Arabic followed by a conversion to Roman again.


Next weekend is Easter, so we'll be writing code to calculate when Easter and its related holidays (Good Friday, Pentecost, etc.) occur for any given year.

Wednesday, March 12, 2008

InstrCount()

This is a special mid-week edition of Reusable Code. While preparing this weekend's edition, I discovered that for the ASP examples we would need another utility function that is useful outside of working with Roman numerals. I feel it important enough to have it's own post, but I don't want to delay the upcoming posts.


ASP programmers are probably familiar with the Instr() function. It returns the location of one string within another. Sometimes you want to know how many times one string occurs within another. PHP programmers have a built-in function called substr_count() which can do this. We're going to write our own function in ASP to duplicate some of this functionality.


  1. function InstrCount(haystack, needle)
  2.     if needle <> "" then
  3.         InstrCount = UBound(Split(haystack, needle))
  4.     end if
  5. end function

It's fairly self-explanatory, but just in case you're not familiar with the Split() function... We are splitting the string into an array, using the search string (needle) as our delimiter. The upper bound of the array gives us the number of needles in our haystack.


Why didn't I call the function substr_count() to match PHP? Normally I do like to have things consistent between languages because it makes it easier for people to go from one to the other, but in this particular case, the PHP function substr_count has two optional arguments. Since ASP does not support optional arguments (at least not the way you would expect), I went with a name more similar to the complementary Instr() function.

Saturday, March 8, 2008

Roman Numerals, Part 1

This weekend we're going to write a function to convert an integer into a Roman numeral, because storing Roman numerals in a database as strings is not cool.


ASP

  1. function roman(ByVal arabic)
  2.     Dim ones
  3.     Dim tens
  4.     Dim hundreds
  5.     Dim thousands
  6.     ones = Array("", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX")
  7.     tens = Array("", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC")
  8.     hundreds = Array("", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM")
  9.     thousands = Array("", "M", "MM", "MMM", "MMMM")
  10.     if arabic > 4999 then
  11.         ' For large numbers (five thousand and above), a bar is placed above a base numeral to indicate multiplication by 1000.
  12.         ' Since it is not possible to illustrate this in plain ASCII, this function will refuse to convert numbers above 4999.
  13.         Err.Clear
  14.         Err.Raise 9
  15.     elseif arabic = 0 then
  16.         ' About 725, Bede or one of his colleagues used the letter N, the initial of nullae,
  17.         ' in a table of epacts, all written in Roman numerals, to indicate zero.
  18.         roman = "N"

  19.     else
  20.         roman = thousands((arabic - (arabic mod 1000)) / 1000)
  21.         arabic = arabic mod 1000
  22.         roman = roman & hundreds((arabic - (arabic mod 100)) / 100)
  23.         arabic = arabic mod 100
  24.         roman = roman & tens((arabic - (arabic mod 10)) / 10)
  25.         arabic = arabic mod 10
  26.         roman = roman & ones((arabic - (arabic mod 1)) / 1)
  27.         arabic = arabic mod 1
  28.     end if
  29. end function

PHP

  1. function roman($arabic)
  2. {
  3.     $ones = Array("", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX");
  4.     $tens = Array("", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC");
  5.     $hundreds = Array("", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM");
  6.     $thousands = Array("", "M", "MM", "MMM", "MMMM");
  7.     if ($arabic > 4999)
  8.     {
  9.         // For large numbers (five thousand and above), a bar is placed above a base numeral to indicate multiplication by 1000.
  10.         // Since it is not possible to illustrate this in plain ASCII, this function will refuse to convert numbers above 4999.
  11.         die("Cannot represent numbers larger than 4999 in plain ASCII.");
  12.     }
  13.     elseif ($arabic == 0)
  14.     {
  15.         // About 725, Bede or one of his colleagues used the letter N, the initial of nullae,
  16.         // in a table of epacts, all written in Roman numerals, to indicate zero.
  17.         return "N";
  18.     }
  19.     else
  20.     {
  21.         $roman = $thousands[($arabic - fmod($arabic, 1000)) / 1000];
  22.         $arabic = fmod($arabic, 1000);
  23.         $roman .= $hundreds[($arabic - fmod($arabic, 100)) / 100];
  24.         $arabic = fmod($arabic, 100);
  25.         $roman .= $tens[($arabic - fmod($arabic, 10)) / 10];
  26.         $arabic = fmod($arabic, 10);
  27.         $roman .= $ones[($arabic - fmod($arabic, 1)) / 1];
  28.         $arabic = fmod($arabic, 1);
  29.         return $roman;
  30.     }
  31. }

You may have noticed in the PHP example that I used the fmod() function instead of the % operator. That was a clue that you might want to add support for floating point integers. You can learn how to represent fractions from the Wikipedia article on Roman numerals. Here's a couple of hints to get you started:


  • You will need another array to hold the numerals for the fractional values.
  • The fractional portion should be handled after the whole number portion has been converted and modularly divided out.
  • However, pre-check the rounding of the fraction before you start the conversion. If it rounds to a whole number, round it off before the conversion to prevent problems.

Next weekend, we're going to write some utility functions that will be useful for turning Roman numerals back into integers, or if you're feeling particularly crazy, performing mathematical calculations on Roman numerals. The weekend after that is Easter weekend, so we'll write a function to calculate Easter and the related holidays surrounding it.

Sunday, March 2, 2008

Leap Years

This year (2008) is a leap year, and this past week contained February 29. It seems only fitting that we talk about calculating leap years and what we can do with that information.


A leap year is always divisible by four, but not by one hundred unless it is also divisible by four hundred.


ASP

  1. function isLeapYear(someYear)
  2.     if someYear Mod 4 = 0 and (someYear Mod 100 <> 0 or (someYear Mod 100 = 0 and someYear Mod 400 = 0)) then
  3.         isLeapYear = True
  4.     else
  5.         isLeapYear = False
  6.     end if
  7. end function

PHP

  1. function isLeapYear($someYear)
  2. {
  3.     return date("L", strtotime($someYear . "-01-01"));
  4. }

Now let's write a function to build on this which returns the number of days in a given month.


ASP

  1. function MonthDays(someMonth, someYear)
  2.     select case someMonth
  3.     case 1, 3, 5, 7, 8, 10, 12
  4.         MonthDays = 31
  5.     case 4, 6, 9, 11
  6.         MonthDays = 30
  7.     case 2
  8.         if isLeapYear(someYear) then
  9.             MonthDays = 29
  10.         else
  11.             MonthDays = 28
  12.         end if
  13.     end select
  14. end function

PHP

  1. function MonthDays($someMonth, $someYear)
  2. {
  3.     return date("t", strtotime($someYear . "-" . $someMonth . "-01"));
  4. }

UPDATE: Thanks to Jim Mayes for showing me more elegant PHP solutions.