4

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 |
Koray Tugay
  • 22,894
  • 45
  • 188
  • 319
Fab
  • 1,145
  • 7
  • 20
  • 40

4 Answers4

4

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);
Mincong Huang
  • 5,284
  • 8
  • 39
  • 62
1

You need sum to be an AtomicLong and perform addAndGet instead of the += because, as the error said, you need sum to be final.

Jeremy Grand
  • 2,300
  • 12
  • 18
0

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.

SachinSarawgi
  • 2,632
  • 20
  • 28
0

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());
D. Werle
  • 176
  • 4