Using HashMaps to map a total and average value to a key

I have a Country class and have read in data from a .csv file which contains many countries names, the region they’re in, the population of each country, the area etc, and stored it in an ArrayList. I am carrying out a data analysis mostly using the java collections framework, and want to find both the total and average population for each region.

I’ve figured using a HashMap is best for this, but I don’t know how to go about this as I’ve never used one before in any complex way or with objects. I also know I’ll have to change the datatype of the int to a long for the total population.

public class Country {
    

    private String name;
    private String region;
    private int population;
    private int area;
    private double density;

    /**
     * Default constructor
     */
    public Country() {

    }

    /**
     * Creates a country with all args
     * 
     * @param name
     * @param region
     * @param population
     * @param area
     * @param density
     */
    public Country(String name, String region, int population, int area, double density) {
        super();
        this.name = name;
        this.region = region;
        this.population = population;
        this.area = area;
        this.density = density;
    }

/**
     * @return the region
     */
    public String getRegion() {
        return region;
    }

    /**
     * @param region the region to set
     */
    public void setRegion(String region) {
        this.region = region;
    }

/**
     * @return the population
     */
    public int getPopulation() {
        return population;
    }

    /**
     * @param population the population to set
     */
    public void setPopulation(int population) {
        this.population = population;
    }



public static void totalPopulationByRegion(Collection<Country> countries) {
        Map<String, Integer> map = new HashMap<String, Integer>();

        int total = 0;

        for (Country country : countries) {
            if (map.containsKey(country.getRegion())) {
                map.put(country.getRegion(), total);
                total+=country.getPopulation();
            } else
                map.put(country.getRegion(), total);
        }

        for (Map.Entry m : map.entrySet()) {
            System.out.println(m.getKey() + " " + m.getValue());
        }
    }

From the output I get on the console I realise my maths logic is all wrong on this, even accounting for the fact that I haven’t dealt with the numbers being too large to store as an int. I get no duplicates for the key which is what I wanted, I just don’t know how to get an accumulative total for the populations which map to each region. Any help with this would be appreciated.

Output I’m getting when called from the main method:

Near east 41843152
Asia -478957430
Europe -7912568
Africa 54079957
Latin amer. & carib 17926472
Northern america -35219702
Baltics -1102504495
Oceania -616300040

Sample from csv file:

Country,Region,Population,Area (sq. mi.)
Afghanistan,ASIA,31056997,647500
Albania,EASTERN EUROPE                     ,3581655,28748
Algeria ,NORTHERN AFRICA                    ,32930091,2381740
American Samoa ,OCEANIA                            ,57794,199
Andorra ,WESTERN EUROPE                     ,71201,468
Angola ,SUB-SAHARAN AFRICA                 ,12127071,1246700
Anguilla ,LATIN AMER. & CARIB    ,13477,102
Antigua & Barbuda ,LATIN AMER. & CARIB    ,69108,443
Argentina ,LATIN AMER. & CARIB    ,39921833,2766890

Answer

Assuming you have already changed the type of population from int to long in your country class

public static class Country {
    private String name;
    private String region;
    private long population;
    ...
}

Here are some ways to achieve what you need:

public static void totalPopulationByRegion(Collection<Country> countries) {
    Map<String, Long> map = new HashMap<>();

    for (Country country : countries) {
        if (map.containsKey(country.getRegion())) {
            //if the map contains the region get the value and add the population of current country
            map.put(country.getRegion(), map.get(country.getRegion()) + country.getPopulation());
        } else{
            //else just put region of current country and population into the map
            map.put(country.getRegion(), country.getPopulation());
        }
    }

    for (Map.Entry m : map.entrySet()) {
        System.out.println(m.getKey() + " " + m.getValue());
    }
}

If you are using Java 8 or higher the above can be shortend using Map#computeIfPresent and Map#computeIfAbsent and avoiding the if else block

public static void totalPopulationByRegion2(Collection<Country> countries) {
    Map<String, Long> map = new HashMap<>();

    for (Country country : countries) {
        map.computeIfPresent(country.getRegion(), (reg, pop)->  pop + country.getPopulation());
        map.computeIfAbsent(country.getRegion(), reg -> country.getPopulation());                   
    }

    for (Map.Entry m : map.entrySet()) {
        System.out.println(m.getKey() + " " + m.getValue());
    }
}

Using the streams API the task to create the map can become a oneliner using Collectors#groupingBy and Collectors#summingLong

public static void totalPopulationByRegion3(Collection<Country> countries) {
    Map<String, Long> map = 
            countries.stream()
                     .collect(Collectors.groupingBy(Country::getRegion, 
                                                    Collectors.summingLong(Country::getPopulation)));

    for (Map.Entry m : map.entrySet()) {
        System.out.println(m.getKey() + " " + m.getValue());
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *