leonardo - Range Titles task in D
View:Recent Entries.
View:Archive.
View:Friends.
View:Profile.
View:Website (My Website).

Tags:, , ,
Security:
Subject:Range Titles task in D
Time:10:59 pm
This page shows a simple programming task:
http://c2.com/cgi/wiki?RangeTitlesInManyProgrammingLanguages

The task:
Show a function that inputs an integer age (or equivalent), and returns a string with a "range name". Here's pseudo-code for the range mapping:

Between 0 And 10 gives "0-10"
Between 11 And 12 gives "11-12"
Between 13 And 14 gives "13-14"
Between 25 And 29 gives "25-29"
Between 30 And 34 gives "30-34"
Between 35 And 39 gives "35-39"
Between 40 And 44 gives "40-44"
Between 45 And 49 gives "45-49"
Between 50 And up gives "50 And Up"
Otherwise, return the specific age (which would occur for 15 through 24)

Of particular interest would be languages that use something more powerful or more compact than the usual if-else or case/switch constructs.

This is a CommonLisp implementation copied from the site, in my opinion it is not short nor sweet, it's long and contains too much tricky code:
(defparameter *default-ranges*
  '((0 . 10)
    (11 . 12)
    (13 . 14)
    (25 . 29)
    (30 . 34)
    (35 . 39)
    (40 . 44)
    (45 . 49)
    (50 . nil)))

(defun age-range (age &optional (ranges *default-ranges*))
  "Return a string for the range containing AGE, or AGE if no range is
  found.  (RANGES is assumed to be in ascending order.)"
  (flet ((in-range-p (age age-pair)
           (or (and (>= age (car age-pair)) (null (cdr age-pair)))
               (<= (car age-pair) age (cdr age-pair)))))
    (let ((range (find-if (lambda (age-pair) (in-range-p age age-pair))
                          ranges)))
      (cond ((null range) (format nil "~A" age))
            ((null (cdr range)) (format nil "~A and up." (car range)))
            (t (format nil "~A-~A" (car range) (cdr range)))))))

This is a C implementation copied from the site, it's a bit better, but it's a little too much hairy (and it may be buggy too):
struct age_range { unsigned int min; unsigned int max; char const * name; };

struct age_range_list { age_range const * age_ranges; size_t count; };

static age_range s_age_ranges[] = {
       {  0, 10, "0-10"  },
       { 11, 12, "11-12" },
       { 13, 14, "13-14" },
       { 15, 15, "15"    },
       { 16, 16, "16"    },
       { 17, 17, "17"    },
       { 18, 18, "18"    },
       { 19, 19, "19"    },
       { 20, 20, "20"    },
       { 21, 21, "21"    },
       { 22, 22, "22"    },
       { 23, 23, "23"    },
       { 24, 24, "24"    },
       { 25, 29, "25-29" },
       { 30, 34, "30-34" },
       { 35, 39, "35-39" },
       { 40, 44, "40-44" },
       { 45, 49, "45-49" },
       { 50, UINT_MAX, "50 And Up" }
};

age_range_list g_age_range_list = { s_age_ranges, arraysizeof(s_age_ranges); };

char const * Get_name_for_age_range(unsigned int age, age_range_list const * age_ranges)
{
       for (size_t i = 0; i < age_ranges->count; ++i)
       {
              if ((i >= age_ranges->age_ranges[i].min) && (i <= age_ranges->age_ranges[i].max))
              {
                     return age_ranges->age_ranges[i].name;
              }
       }

       return "Invalid Age";
}

This is my D V2 translation of the C version, it's simpler to read and less hairy:
import std.stdio: writeln;

string getNameForAgeRange(int age) {
    static struct AgeRange {
        int min, max;
        string name;
    }

    enum AgeRange[] ageRanges = [
        { 0, 10, "0-10" },
        {11, 12, "11-12"},
        {13, 14, "13-14"},
        {15, 15, "15"   },
        {16, 16, "16"   },
        {17, 17, "17"   },
        {18, 18, "18"   },
        {19, 19, "19"   },
        {20, 20, "20"   },
        {21, 21, "21"   },
        {22, 22, "22"   },
        {23, 23, "23"   },
        {24, 24, "24"   },
        {25, 29, "25-29"},
        {30, 34, "30-34"},
        {35, 39, "35-39"},
        {40, 44, "40-44"},
        {45, 49, "45-49"},
        {50, AgeRange.max.max, "50 And Up"}
    ];

    if (age >= 0)
        foreach (ref arange; ageRanges)
            if (age >= arange.min && age <= arange.max)
                return arange.name;
    return "Invalid Age";
}

void main() {
    writeln(getNameForAgeRange(0));
}

But in practice that's not the D code I'd use. In real programming it's better to write code that's clean, easy to read and not bug-prone, this means that sometimes some duplication (or some usage of data instead of code) helps:
import std.conv: to;

string getNameForAgeRange(int age) {
    if (age < 0)
        return "Invalid Age";

    switch (age) {
        case  0: .. case 10: return "0-10";
        case 11: .. case 12: return "11-12";
        case 13: .. case 14: return "13-14";
        case 15: .. case 24: return to!string(age);
        case 25: .. case 29: return "25-29";
        case 30: .. case 34: return "30-34";
        case 35: .. case 39: return "35-39";
        case 40: .. case 44: return "40-44";
        case 45: .. case 49: return "45-49";
        default:             return "50 And Up";
    }
}

void main() {
    assert(getNameForAgeRange(20) == "20");
}

If performance is very important you may replace that to!string() with strings (D compilers are probably unable to perform this simple optimization by themselves):
        case 15:             return "15";
        case 16:             return "16";
...

That D case range syntax is not the best looking. This looks better (but it not supported, a similar syntax is one of the infinite GCC C Extensions:http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Case-Ranges.html ):
string getNameForAgeRange(int age) {
    final switch (age) {
        case    ... -1: return "Invalid Age";
        case  0 ... 10: return "0-10";
        case 11 ... 12: return "11-12";
        case 13 ... 14: return "13-14";
        case 15 ... 24: return to!string(age);
        case 25 ... 29: return "25-29";
        case 30 ... 34: return "30-34";
        case 35 ... 39: return "35-39";
        case 40 ... 44: return "40-44";
        case 45 ... 49: return "45-49";
        case 50 ...   : return "50 And Up";
    }
}

It's better to minimize code when it may become a source of bugs.

---------------------------

Update Nov 16 2010: some comments on Reddit suggest that D code like this is more correct:
string ageRangeName(int age)
    in {
        assert(age >= 0, "Invalid age");
    } body {
        switch (age) {
            case  0: .. case 10: return "0-10";
            case 11: .. case 12: return "11-12";
            case 13: .. case 14: return "13-14";
            case 15: .. case 24: return to!string(age);
            case 25: .. case 29: return "25-29";
            case 30: .. case 34: return "30-34";
            case 35: .. case 39: return "35-39";
            case 40: .. case 44: return "40-44";
            case 45: .. case 49: return "45-49";
            default:             return "50 And Up";
        }
    }
Often it's better to write the simplest code able to do the work, because simple code contains less bugs, and it's simpler to understand and mantain. Often in the real world brutally simple (but not too much simple) code is the best. Bugs may be present both in algorithmic code and data, so it's a matter of finding the best middle point able to minimize the probability of bugs.

With a much longer table, using algorithmic code to generate this answer is better. But in this problem there are only few cases, so this looks like a better solution (easy to modify too).
comments: Leave a comment Previous Entry Add to Memories Share Next Entry

leonardo - Range Titles task in D
View:Recent Entries.
View:Archive.
View:Friends.
View:Profile.
View:Website (My Website).