Range Titles In Many Programming Languages

This is based on an actual charting request to groups peoples' ages rather than list each specific age. The ages are skewed to reflect late teens, because that is the area of the most intense study for this domain. 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.

Of interest to whom? PrincipleOfLeastPower and PrincipleOfLeastPrivilege would suggest we use a simple pattern-matching function (~eqv. to if-else, minus side-effects and fall-through). If you wanted an uber-powerful system, we could apply a plugin-extensible AbstractFactory that loads DLLs or otherwise dynamically obtains code from a common service source, performs pattern-matching over the input (perhaps a pseudo-URI 'age_descriptor:Integer' in this case), and provides a neat little XAML object that happens to contain a simple string for display. That might be interesting to someone, but it's just slightly excessive for this problem.

You could also suggest this task on RosettaCode, which so far has only a smattering of relational examples.

I doubt a relational approach would, for solving this problem, be any more compact or powerful than an if-else or case/switch construct. What makes you call it a relational example?


Here's a relational example (!?) in TutorialDee:

 OPERATOR AgeToRangeTitle(age INTEGER) RETURNS CHAR;
   RETURN 
     title FROM TUPLE FROM (
       RELATION {
         TUPLE {lower 0, upper 10, title "0-10"},
         TUPLE {lower 11, upper 12, title "11-12"},
         TUPLE {lower 13, upper 14, title "13-14"},
         TUPLE {lower 15, upper 24, title CAST_AS_CHAR(age)},
         TUPLE {lower 25, upper 29, title "25-29"},
         TUPLE {lower 30, upper 34, title "30-34"},
         TUPLE {lower 35, upper 39, title "35-39"},
         TUPLE {lower 40, upper 44, title "40-44"},
         TUPLE {lower 45, upper 49, title "45-49"},
         TUPLE {lower 50, upper 999, title "50 And Up"}
       } 
       WHERE (age >= lower AND age <= upper));
 END OPERATOR;
Use of upper-case is to distinguish keywords from identifiers. It is not a language requirement.

Here's a non-relational example in TutorialDee:

 OPERATOR AgeToRangeTitle2(age INTEGER) RETURNS CHAR;
   RETURN 
     CASE
       WHEN age >= 0 AND age <= 10 THEN "0-10" 
       WHEN age >= 11 AND age <= 12 THEN "11-12"
       WHEN age >= 13 AND age <= 14 THEN "13-14"
       WHEN age >= 25 AND age <= 29 THEN "25-29"
       WHEN age >= 30 AND age <= 34 THEN "30-34"
       WHEN age >= 35 AND age <= 39 THEN "35-39"
       WHEN age >= 40 AND age <= 44 THEN "40-44"
       WHEN age >= 45 AND age <= 49 THEN "45-49"
       WHEN age >= 50 AND age <= 999 THEN "50 And Up"
       ELSE CAST_AS_CHAR(age)
     END CASE;
 END OPERATOR;
The latter is somewhat simpler than the former. However, if we assume the range definitions are in a pre-existing RelVar called AgeRanges (defined as above but without TUPLE {lower 15, upper 24, title CAST_AS_CHAR(age)}), we can alter the ranges without changing the source code and so the first example becomes:

 OPERATOR AgeToRangeTitle(age INTEGER) RETURNS CHAR;
   RETURN 
     WITH 
       AgeRanges WHERE (age >= lower AND age <= upper) AS selectedRange : 
       IF IS_EMPTY(selectedRange) THEN 
         CAST_AS_CHAR(age) 
       ELSE 
         title FROM TUPLE FROM selectedRange
       END IF;
 END OPERATOR;
If we have multiple range definitions or wish to create them on the fly, we can pass an age range relation as a parameter:

 OPERATOR AgeToRangeTitle(age INTEGER, ageRanges RELATION {lower INTEGER, upper INTEGER, title CHAR}) RETURNS CHAR;
   RETURN 
     WITH 
       ageRanges WHERE (age >= lower AND age <= upper) AS selectedRange : 
       IF IS_EMPTY(selectedRange) THEN 
         CAST_AS_CHAR(age) 
       ELSE 
         title FROM TUPLE FROM selectedRange
       END IF;
 END OPERATOR;


In either C or C++ I would probably do something similar to the TutorialDee above. arraysizeof is a preprocessor macro that calculates the number of elements in an array.

 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"; }


I had in mind something like this pseudo-code:

 With i do listFold on x,y: list=((0,10)(11,12)(13,14) etc...)
   If i between x and y return(x . '-' . y) 
   else if i >= 50 return "50-and-up" 
   else return i
This is the TutorialDee equivalent, which determines the upper end of the age range (the "50-and-up") from the maximum in the age range relation, and supports both the specified ranges and a user-specified age range relation:
 OPERATOR AgeToRangeTitle(age INTEGER, ageRange RELATION {lower INTEGER, upper INTEGER}) RETURNS CHAR;
   RETURN 
     WITH 
       ageRange WHERE (age >= lower AND age <= upper) AS selectedRange,
       MAX(ageRange, upper) AS maxAge :
       CASE
         WHEN age > maxAge THEN CAST_AS_CHAR(maxAge + 1) || '-and-up'
         WHEN IS_EMPTY(selectedRange) THEN CAST_AS_CHAR(age) 
         ELSE title FROM TUPLE FROM 
           EXTEND selectedRange ADD (CAST_AS_CHAR(lower) || '-' || CAST_AS_CHAR(upper) AS title) 
       END CASE;
 END OPERATOR;

OPERATOR AgeToRangeTitle(age INTEGER) RETURNS CHAR; RETURN AgeToRangeTitle(age, RELATION { TUPLE {lower 0, upper 10}, TUPLE {lower 11, upper 12}, TUPLE {lower 13, upper 14}, TUPLE {lower 25, upper 29}, TUPLE {lower 30, upper 34}, TUPLE {lower 35, upper 39}, TUPLE {lower 40, upper 44}, TUPLE {lower 45, upper 49} }); END OPERATOR;
The TutorialDee examples have all been tested using the RelProject. But if we permit pseudo-code, why not:
 <lower, upper>(0,10; 11,12; 13,14; 25,29; 30,34; 35,39; 40,44; 45,49)
   when (lower <= age <= upper) lower '-' upper 
   when (age >= 50) '50-and-up' 
   else age
Coming up with minimal but conceivable code to achieve some goal in a fictional language is a weird game. I find it oddly amusing.


FirstClass composable pattern-matching:

 type pattern A B = (A -> Maybe B)
 patternFromPatternList H:T = \X -> 
       case (H X) of 
         Nothing => patternFromPatternList T X
       | Maybe S => Maybe S
 patternFromPatternList [] = \X -> Nothing
 ;
 matchRange L U F X = if((L <= X) and (X <= U)) then (Just (F X)) else Nothing 
 ;
 ages = [(0,10),(11,12),(13,14),(25,29),(30,34),(35,39),(40,44),(45,49)]
 showRange A B = (show A) ++ "-" ++ (show B)
 ;
 ageToRange = 
  let ar A B = matchRange A B (showRange A B)
  in patternFromPatternList ((map ar ages) ++ [
     ,\x -> if(x >= 50) then Just "50 And Up" else Nothing
     ,\x -> Just (show x)] 

This is just slightly less powerful than the plugin-extensible AbstractFactory approach mentioned at the top, weaker only because the list passed to 'patternFromPatternList' has a centralized definition rather than a distributed, external, dynamic one.


What's with all the hard coding of parameters?

CommonLisp:

  (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)))))))

Short, sweet, and to the point.

That doesn't look very short to me. A plain-jane C/JavaScript-like approach would be shorter I do believe. However, I agree it may scale better.

It's 9 lines of non-comment source, how short do you want it? I could probably shave two more lines by putting the body of in-range-p directly in the lambda, but that would be less readable. You could implement the same approach in C, of course, but I don't think it would be shorter. Certainly not sweeter. ;-)

[There's a seven line C example on this page. It doesn't have the string handling or default age ranges, but it does have the advantage of being able to handle non-standard titles for the age ranges. The lisp code here does not. BTW, adding the default parameter would take one line, and adding the string handling would add two. Not really much of a difference in either case. This has more to do with the simplicity of the problem rather than in advantages/disadvantages of the languages.]

[It's probably short, sweet, and to the point if you LISP. Note that a true Lisper only judges other LISP; all non-LISP code is inherently flawed because it isn't LISP.]

But this is the C2 Wiki! Everyone here knows SmallTalk and Lisp. Right?


SchemeLanguage

 (define rrr
    '((0 . 10)
      (11 . 12)
      (13 . 14)
      (25 . 29)
      (30 . 34)
      (35 . 39)
      (40 . 44)
      (45 . 49)
      (50 . ())))

(define (age-range age ranges) (let ((range (car ranges))) (cond ((or (null? range) (< age (car range))) age) ((or (null? (cdr range)) (<= age (cdr range))) range) (else (age-range age (cdr ranges))) )))

(define (display-range range) (if (cons? range) (if (null? (cdr range)) (printf "~a and up\n" (car range)) (printf "~a-~a\n" (car range) (cdr range)) ) (printf "~a\n" range) ))

(define (display-age-range age ranges) (display-range (age-range age ranges)))

> (display-age-range 55 rrr) 50 and up


OcamlLanguage

 (* F:\Programming\Objective Caml\AgeRange5.ml *)

let age_range min max missing age = if min <= age && age <= max then (string_of_int min) ^ "-" ^ (string_of_int max) else missing age;;

let specific_age min max missing age = if min <= age && age <= max then (string_of_int age) else missing age;;

let age_and_up target missing age = if target <= age then (string_of_int target) ^ " and Up" else missing age;;

let age_ranges = [ age_range 0 10; age_range 11 12; age_range 13 14; specific_age 15 24; age_range 25 29; age_range 30 34; age_range 35 39; age_range 40 44; age_range 45 49; age_and_up 50 ];;

let rec chain ranges age = match ranges with [] -> "Unknown" | head :: tail -> head (chain tail) age;;

let string_of_age = chain age_ranges;;

(* Examples of use *) # string_of_age 0;; - : string = "0-10" # string_of_age 18;; - : string = "18" # string_of_age 75;; - : string = "50 and Up"


Python:

def age_range_name(age):

    if age < 0: return 'Invalid Age'
    elif 15 <= age <= 24: return str(age)
    elif age >= 50 : return '50 And Up'
    ranges = ((0,10), (11, 12), (13, 14), (25, 29), (30, 34), (35, 39),
              (40, 44), (45, 49))
    for r in ranges:
        if r[0] <= age <= r[1]:
            return '%s-%s' %r


CategoryInManyProgrammingLanguages


EditText of this page (last edited November 15, 2010) or FindPage with title or text search