# Counting persons in each age range

I am calculating the number of people within a given age range. This is my current approach.

Note:

• The persons is a List<Person>
• The getDiffYears() returns the number of years between now and DOB of the person. It includes the month and day as well.
• The method will be returning a HashMap<String, Integer>() (Can be changed if there is a better alternative.)

The code has been tested and is working. I wanted to know if there is a more efficient approach without using this many if statements?

for (Person p: persons) {
Date dob = p.getDob();
if (dob != null) {
years = getDiffYears(p.getDob());
if (years < 10) {
a++;
} else if (years < 20) {
b++;
} else if (years < 30) {
c++;
} else if (years < 40) {
d++;
} else if (years < 50) {
e++;
} else if (years < 60) {
f++;
} else if (years < 70) {
g++;
} else if (years < 80) {
h++;
} else if (years < 90) {
i++;
} else if (years < 100) {
j++;
} else {
k++;
}
} else {
k++;
}
}
map.put("00 - 09", a);
map.put("10 - 19", a);
map.put("20 - 29", b);
map.put("30 - 39", c);
map.put("40 - 49", e);
map.put("50 - 59", f);
map.put("60 - 69", g);
map.put("70 - 79", h);
map.put("80 - 89", i);
map.put("90 - 99", j);
map.put("Unknown", k);

• Welcome to CR! Are you on Java 8? Commented Jan 8, 2016 at 15:15
• I would argue that being over 100 is not unknown just in the category 100+. Since you're using range maybe 100-infinity (I know it's useful for you probably, I'm just pointing it out) Commented Jan 8, 2016 at 15:19
• @h.j.k. Yes, I am on Java 8. Commented Jan 8, 2016 at 15:30
• @Marc-Andre I have them in the same variable as the count won't be used for anything. It is there to simply know how many people's age were not used. Commented Jan 8, 2016 at 15:31
• @miqdadamirali I do understand this but I just wanted to point out that classifying people over 100 in a category Unknown is not accurate since you do know their age and in which "range" they are. I suspected you did not use that "range" and why you put it in an unused category. Commented Jan 8, 2016 at 15:42

First of all you have a typo with your values. You accidentally have a twice when putting to your map, and then you end up missing d:

map.put("00 - 09", a);
map.put("10 - 19", a);
map.put("20 - 29", b);
map.put("30 - 39", c);
map.put("40 - 49", e);


These single letter variables aren't helping you. Instead have an int array that you increment. You could then just increment by index, and since you're using a simple numerical set up, you could just use years/10 as your index.

Something like this:

int counts[] = new int[11];
if (years == null or years > 100){
counts[10]++;
} else {
counts[years/10]++;
}


ints are rounded down so if years = 19 then you get 19/10, which is 1.

You can then use these values for your mapping:

map.put("00 - 09", counts[0]);
map.put("10 - 19", counts[1]);
map.put("20 - 29", counts[2]);


I'm sure there's a way to loop over those or make the mapping more directly, but I'm not personally familiar with it.

• Thank you for pointing out the mistake :). 1. The issue I run into when using the array is that I do not have the option of also storing the range the count corresponds to. 2. A number such as 15, when divided by 10. Would the result be rounded up or down? because you can't have value at index position 1.5. Commented Jan 8, 2016 at 15:29
• @miqdadamirali integers are rounded down, so even 19/10 becomes 1. Also I wasn't suggesting you just keep the array, I intended for you to then use putat the end with the array values. I'll amend both these notes. Commented Jan 8, 2016 at 15:31
• You made a good point. I'll wait out a day and see if someone else responds. Thanks Commented Jan 8, 2016 at 15:38
• Aren't the indices 0-10 (so 11 elements?) Wouldn't [11] overflow? Commented Jan 8, 2016 at 16:07
• @BenjaminGruenbaum You are correct, I rounded it right at first then did it wrong when figuring out what index "Unknown" should be. Commented Jan 8, 2016 at 16:12

### Java 8 streams

Aside from the typos pointed out by @SuperBiasedMan, you can use Java 8's stream-based processing as an alternative for generating your Map result.

private static final int UPPER_LIMIT = 100;
private static final String[] KEYS = new String[]{ "00 - 09", /* ... */ "Unknown" };

private static int categorize(int year) {
return Math.min(year, UPPER_LIMIT) / 10;
}

Map<String, Long> results = persons.stream()
.map(Person::getDob)
.filter(Objects::nonNull)
.map(ThisClass::getDiffYears)
.map(ThisClass::categorize)
.collect(Collectors.groupingBy(ageCategory -> KEYS[ageCategory],
Collectors.counting()));

1. Define the UPPER_LIMIT of your age categorization and the desired category names.
2. From a Stream of your Person objects, map() to their birthdays by calling Person::getDob as method reference.
3. filter() null birthdays by using Objects::nonNull.
4. map() birthdays to ages by calling your getDiffYears(Date) method. Note that if it is not a static method, the method reference then becomes this::getDiffYears.
5. map() ages to the age category by calling categorize(int) to get the desired array indices for KEYS. If you have not realized yet, ThisClass is just a placeholder for the actual class name.
6. collect() the age categories into the desired Map result groupingBy() our categorization and counting() the elements per category.

• Your method should return a Map instead of HashMap so that callers of this method only need to deal with the interface, and not the implementation.
• You may want to consider how to handle for null inputs to getDiffYear(Date). Should it return a negative value?
• You can also experiment with deriving the category names in a programmatic manner, instead of using literal Strings.
• You can even think about making your age categorization depend on a configurable upper limit (100) and band (10). For example, how easy will it be to treat the upper limit as 101 (UPPER_LIMIT = 101) and a band of 11 years per category (hint: take a closer look at the suggested code...)?