# Classifying by age

Problem:

Classify the user (such as infant, child, teenager, adult) based on user's age.

Solution

The code snippet written below works well as expected. I want to know if there's a better way to do or refine this.

package main

import (
"errors"
"fmt"
)

func age(urAge int) (int, string, error) {
if urAge > 120 || urAge < 0 {
return -1, "", errors.New("Invalid age")
} else if urAge > 0 && urAge <= 2 {
return urAge, "Infant", nil
} else if urAge > 2 && urAge <= 12 {
return urAge, "Child", nil
} else if urAge > 12 && urAge <= 19 {
return urAge, "Teenager", nil
} else {
}
}

func main() {
var input int
fmt.Scanf("%d", &input)

if a, b, e := age(input); e != nil {
fmt.Println("Error: ", e)
} else {
fmt.Println("Age: ", a)
fmt.Println("Classification: ", b)
}
}


Note that your code classifies age = 0 as "Adult" which you probably want to be "Infant". (This is because age = 0 is not included in any of your specified ranges and so the last else branch will be executed.)

Two completely different solutions will be presented:

1. The clearest and most obvious using switch.
2. An elegant, very compact and faster way using binary search.

# 1) Using switch

A much clearer way would be to use switch.

If you write cases in increasing age order, you don't need to explicitly state both boundaries of a classification because cases are evaluated in order and the first one that matches will be executed:

func age(urAge int) (int, string, error) {
switch {
case urAge < 0 || urAge > 120:
return -1, "", errors.New("Invalid age")
case urAge <= 2:
return urAge, "Infant", nil
case urAge <= 12:
return urAge, "Child", nil
case urAge <= 19:
return urAge, "Teenager", nil
default:
}
}


Also I find it redundant to return 3 values: age, classification and an error. Since the returned age is identical to the input if no error is returned, it is unnecessary. Callers of your function will know not to use the age if an error is returned, so I would further simplify it like this:

func age(urAge int) (string, error) {
switch {
case urAge < 0 || urAge > 120:
return "", errors.New("Invalid age")
case urAge <= 2:
return "Infant", nil
case urAge <= 12:
return "Child", nil
case urAge <= 19:
return "Teenager", nil
default:
}
}


And then of course usage slightly changes (slightly simplifies):

if b, e := age(input); e != nil {
fmt.Println("Error: ", e)
} else {
fmt.Println("Age: ", input)
fmt.Println("Classification: ", b)
}


### Alternative error reporting

Although in general it is a good pattern to return a value and an error if a function can fail, in your case it doesn't really serve much purpose. It can only be "Invalid age". Also the empty string "" does not have a meaning as a classification, so you may want to exploit the empty string "" value to signal the error, and then you only need to return one value. If you choose to do this, the minimum is to include this in the documentation of the function like this:

// age returns the age classification.
// Returns empty string "" if the age is invalid.
func age(urAge int) string {
switch {
case urAge < 0 || urAge > 120:
return "" // Empty string means "Invalid age"
case urAge <= 2:
return "Infant"
case urAge <= 12:
return "Child"
case urAge <= 19:
return "Teenager"
default:
}
}


And using it:

if c := age(input); c == "" {
fmt.Println("Error: Invalid age")
} else {
fmt.Println("Age: ", input)
fmt.Println("Classification: ", c)
}


# 2) Using binary search

There is another elegant way. This is also faster if there are many cases. This solution is possible because age ranges of different classifications are disjunct (that is a concrete age can only belong to one classification). It also makes our life easier that age ranges of classifications are contiguous (we would need more ranges to model otherwise, but it wouldn't be a show-stopper).

The idea is that we list the age boundaries between classifications in an int slice in ascending order, and we can perform a binary search in this sorted slice. We can just model ranges with their maximum value since the sequence of ranges is without any holes. This will give us the index of the classification. We can store the classification names in a separate slice, and return the classification name at the index we just found as the result of binary search.

Binary search in a sorted slice is implemented in the standard library, we can just use that: sort.SearchInts().

Note that we will put 2 empty string names into the name slice (to the first and last place) which will be for the invalid ages (<0 and >120).

Now let's see the implementation, it is rather short:

var ages = []int{-1, 2, 12, 19, 120}
var classes = []string{"", "Infant", "Child", "Teenager", "Adult", ""}

// age returns the age classification.
// Returns empty string "" if the age is invalid.
func age(urAge int) string {
return classes[sort.SearchInts(ages, urAge)]
}


That's all! It's a one-line function!

Testing it:

data := []int{-100, -1, 0, 2, 3, 10, 12, 13, 19, 20, 120, 121}
for _, d := range data {
if c := age(d); c == "" {
fmt.Printf("Age: %4d, Classification: Invalid age\n", d)
} else {
fmt.Printf("Age: %4d, Classification: %s\n", d, age(d))
}
}


Output (try it on the Go Playground):

Age: -100, Classification: Invalid age
Age:   -1, Classification: Invalid age
Age:    0, Classification: Infant
Age:    2, Classification: Infant
Age:    3, Classification: Child
Age:   10, Classification: Child
Age:   12, Classification: Child
Age:   13, Classification: Teenager
Age:   19, Classification: Teenager