Daniel O Mensah
Daniel O Mensah

Reputation: 406

how to calculate average of hashmap values with same key

So I have this program that calculates the sum of all the petshops with the same key but different values. However, now, I would like to calculate the average of each petshop with the same key. I was thinking about using a counter in order to get how many times a petshop is contained in the arraylist. But it does not work. would I need to run another for each loop?

public class AverageCost {

    public void calc(ArrayList<Pet> pets) { 

        Map<String, Double> hm = new HashMap<>();

        for (Pet i : pets) {
            String name = i.getShop();
            // If the map already has the pet use the current value, otherwise 0.
            double price = hm.containsKey(name) ? hm.get(name) : 0;
            price += i.getPrice();
            hm.put(name, price);

        }
        System.out.println("");
        for (String key : hm.keySet()) {
            System.out.printf("%s: %s%n", key, hm.get(key));
        }

    }

Upvotes: 1

Views: 8778

Answers (3)

atr
atr

Reputation: 714

You can use the Collections.frequency to get the number of occurrence and divide the whole sum

for (String key : hm.keySet()) {
    int w = Collections.frequency(pets, new Pet(key));
    System.out.printf("%s: %s%n", key, hm.get(key)/w);
}

Upvotes: 1

johnsgp
johnsgp

Reputation: 103

What you are asking for is an algorithm to calculate the cumulative moving average without storing the number of terms you have so far accumulated. I don't think this is possible (for example see https://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average where 'n', the number of terms so far, is required). My suggestion is to use two passes - the first to store the numbers and the second to calculate the averages.

public void calc(List<Pet> pets) {
    // First pass
    Map<String, List<Double>> firstPass = new HashMap<>();
    for (Pet pet : pets) {
        String name = pet.getShop();
        if (firstPass.containsKey(name)) {
            firstPass.get(name).add(pet.getPrice());
        } else {
            List<Double> prices = new ArrayList<>();
            prices.add(pet.getPrice());
            firstPass.put(name, prices);
        }
    }

    // Second pass
    Map<String, Double> results = new HashMap<>();
    for (Map.Entry<String, List<Double>> entry : firstPass.entrySet()) {
        Double average = calcAverage(entry.getValue());
        results.put(entry.getKey(), average);
        // Print results
        System.out.printf("%s: %s%n", entry.getKey(), average);
    }
}

private double calcAverage(List<Double> values) {
    double result = 0;
    for (Double value : values) {
        result += value;
    }
    return result / values.size();
}

Upvotes: 1

hoaz
hoaz

Reputation: 10153

You can introduce second map for counting or use compound value object in your map to hold both accumulated price and number of pets:

Map<String, PetStatistics> hm = new HashMap<>();
for (Pet i : pets) {
    String name = i.getShop();
    // If the map already has the pet use the current value, otherwise 0.

    PetStatistics stats = hm.get(name);
    if (stats == null) {
        stats = new PetStatistics(0, 0); // count and price
        hm.put(name, stats);
    }
    stats.addPrice(i.getPrice());
    stats.incrementCount();
}

Upvotes: 1

Related Questions