1

I have a Map<String, Integer> and an integer named globalValue. My goal is to replace values in map with percentage of globalValue, eg; (value1/globalValue)*100 and round the final number.

I have tried doing division directly in the map (which resulted in 0) and also trying to convert my integers to double both in the map and outside the map (both resulted in type mismatch).

public Map<String, Integer> analyse() {
    int globalValue = 0;
    Map<String, Integer> m = new HashMap<String, Integer>();
    for (int i = 0; i < array.length; i++) {
        if (array[i].getTime() != null) {
            globalValue = globalValue + array[i].getValue();
            if (m.containsKey(array[i].getTime())) {
                m.put(array[i].getTime(), m.get(array[i].getTime()) + array[i].getValue());
            } else {
                m.put(array[i].getTime(), array[i].getValue());
            }
        }
    }
    return m;
}
Ousmane D.
  • 50,173
  • 8
  • 66
  • 103
  • 1
    Why not make your map of `Double` instead of `Integer`? – GBlodgett Dec 27 '18 at 15:36
  • 1
    Shed some details on your `array` – Ram Dec 27 '18 at 15:37
  • 1
    there is no `(value1/globalValue)*100` division logic in code, can you explain what exactly you need? either division logic? or rephrase the above code? – Deadpool Dec 27 '18 at 15:38
  • As GBlodgett suggested use a Double instead or try casting the value `(Integer)`? – Nikhil Dec 27 '18 at 15:39
  • 1
    "I have tried doing division directly in the map (which resulted in 0)" most likely because you're performing integer division. make one of the operands a double. nevertheless, your current code doesn't show any of that logic.... – Ousmane D. Dec 27 '18 at 15:44
  • @GBlodgett return type has to be integer –  Dec 27 '18 at 15:59

1 Answers1

3

This would be much simpler using the Stream API. You also need to use double or Double to avoid rounding all values down to 0.

public Map<String, Double> analyse() {
    double sum = Stream.of(array).mapToDouble(d -> d.getValue()).sum();
    return Stream.of(array)
            .collect(Collectors.groupingBy(t -> t.getTime(),
                    Collectors.summingDouble(t -> t.getValue() * 100.0 / sum)));
}

Why does this happen with int values?

Int division: Why is the result of 1/3 == 0?

Most children learn integer division in primary school but seem to forget all about it once we learn decimals.

To return Integer percentages without too much loos of precision.

public Map<String, Integer> analyse() {
    long sum = Stream.of(array).mapToLong(d -> d.getValue()).sum();
    Map<String, Long> map = Stream.of(array)
            .collect(Collectors.groupingBy(t -> t.getTime(),
                    Collectors.summingLong(t -> t.getValue())));
    return map.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey,
                    e -> (int) Math.round(100.0 * e.getValue() / sum)));
}

This handles the situation where many small values can add up to 1%.

Peter Lawrey
  • 498,481
  • 72
  • 700
  • 1,075
  • well, my return type has to be integer. –  Dec 27 '18 at 15:58
  • @J.Doe then return an integer.. just don't do the calculation with integers – xtratic Dec 27 '18 at 16:04
  • @J.Doe I have added a solution which calculates an Integer reducing the loss of precision. – Peter Lawrey Dec 27 '18 at 16:05
  • @PeterLawrey also the last `100` should be `100.0` right? – xtratic Dec 27 '18 at 16:09
  • 1
    @xtratic it depends on whether an overflow of `100 * long` or a loss of precision from `100.0 * long` is more likely. – Peter Lawrey Dec 27 '18 at 16:10
  • @PeterLawrey Ahh good point. I'd thought you'd just missed it while doing your edits. I didn't read carefully enough, my bad. – xtratic Dec 27 '18 at 16:12
  • @xtratic that is why I used longs for calculations to avoid overflows. – Peter Lawrey Dec 27 '18 at 16:16
  • @PeterLawrey actually without `100.0` then `100 * e.getValue() / sum` will truncate to a `long` and `Math.round` will essentially do nothing, right? I simplified to this, eliminating the intermediate map: `long sum = Stream.of(array).mapToLong(o -> o.getValue()).sum(); Map m = Stream.of(array).collect(Collectors.toMap(o -> o.getTime(), o -> Math.round(100d * o.getValue() / sum)));` – xtratic Dec 27 '18 at 16:52
  • 1
    @xtratic good point, in the first example `sum` as ` double` but I dropped it in the second case. – Peter Lawrey Dec 27 '18 at 16:54
  • @PeterLawrey Yep, I figured it was just an error in the copy pasting and editing. – xtratic Dec 27 '18 at 16:55
  • Why the intermediate map though? – xtratic Dec 27 '18 at 16:56