Reputation: 1215
here my current code to perform a cumulative sum over a hash table Map< String,Double>
START.forEach((k,v)->{
sum += v;
END.put(k, sum);
});
or, alternately,
END= START.entrySet()
.stream()
.collect(
Collectors.toMap(entry -> entry.getKey(),
entry -> {
sum += entry.getValue();
return sum;
}));
But I have the following error:
Local variable sum defined in an enclosing scope must be final or effectively final
How could I fix it?
I wouldn't want to use the standard for loop like that:
Iterator it = START.entrySet().iterator();
double sum = 0;
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
String key = (String) pair.getKey();
Double value = (Double) pair.getValue();
sum+=value;
END.put(date, sum);
}
START
------------
|first | 1 |
|second| 5 |
|third | 4 |
END
|first | 1 |
|second| 6 |
|third | 10 |
Upvotes: 4
Views: 6268
Reputation: 5552
Entries' order is important for cumulative sum. If you're using HashMap
as implementation, it makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time. So I recommend you to use another implementation, like LinkedHashMap
. It use hash table and linked list implementation of the Map interface, with predictable iteration order.
Map<String, Double> map = new LinkedHashMap<>();
map.put("first", 1.0);
map.put("second", 5.0);
map.put("third", 4.0);
Use atomic reference to avoid "final" problem. In Java, you can't use non-final variables in lambda as well as in anonymous inner classes. That's why you got the message "Local variable sum defined in an enclosing scope must be final or effectively final". Then, you can define the binary operator as (x, y) -> x + y
, since you wish to summarize the current entry's value with the previous cumulative sum.
AtomicReference<Double> atomicSum = new AtomicReference<>(0.0);
map.entrySet().forEach(e -> e.setValue(
atomicSum.accumulateAndGet(e.getValue(), (x, y) -> x + y)
));
Here's the final code.
Map<String, Double> map = new LinkedHashMap<>();
map.put("first", 1.0);
map.put("second", 5.0);
map.put("third", 4.0);
AtomicReference<Double> atomicSum = new AtomicReference<>(0.0);
map.entrySet().forEach(e -> e.setValue(
atomicSum.accumulateAndGet(e.getValue(), (x, y) -> x + y)
));
// tested in JUnit
assertEquals(10.0, atomicSum.get(), 0.0001);
assertEquals(1.0, map.get("first"), 0.0001);
assertEquals(6.0, map.get("second"), 0.0001);
assertEquals(10.0, map.get("third"), 0.0001);
Upvotes: 4
Reputation: 186
You can use an java.util.concurrent.DoubleAdder
and Collectors#toMap
like this:
final Map<String, Double> START = new HashMap<>();
START.put("first", 1.0);
START.put("second", 5.0);
START.put("third", 4.0);
System.out.println(START.toString());
DoubleAdder sum = new DoubleAdder();
Map<String, Double> cumulativeSum = START.entrySet().stream().sequential().collect(
Collectors.toMap(Entry::getKey, it -> { sum.add(it.getValue()); return sum.sum(); }));
System.out.println(cumulativeSum.toString());
Upvotes: 0
Reputation: 2692
I do not know but this snippet of code may help you.
List<Integer> ints = new ArrayList<>();
ints.add(1);
ints.add(2);
ints.add(3);
AtomicInteger sum = new AtomicInteger(0);
ints.stream().sequential().mapToInt(sum::addAndGet).forEach(System.out::println);
Try to modify and use this code snippet in your code.
Upvotes: 0
Reputation: 2370
You need sum
to be an AtomicLong
and perform addAndGet
instead of the +=
because, as the error said, you need sum to be final.
Upvotes: 1