Issue with Java 8 Lambda for effective final while incrementing counts - java

I want to use Java 8 Lambda expression in following scenario but I am getting Local variable fooCount defined in an enclosing scope must be final or effectively final. I understand what the error message says, but I need to calculate percentage here so need to increment fooCount and barCount then calculate percentage. So what's the way to achieve it:
// key is a String with values like "FOO;SomethinElse" and value is Long
final Map<String, Long> map = null;
....
private int calculateFooPercentage() {
long fooCount = 0L;
long barCount = 0L;
map.forEach((k, v) -> {
if (k.contains("FOO")) {
fooCount++;
} else {
barCount++;
}
});
final int fooPercentage = 0;
//Rest of the logic to calculate percentage
....
return fooPercentage;
}
One option I have is to use AtomicLong here instead of long but I would like to avoid it, so later if possible I want to use parallel stream here.

There is a count method in stream to do counts for you.
long fooCount = map.keySet().stream().filter(k -> k.contains("FOO")).count();
long barCount = map.size() - fooCount;
If you want parallelisation, change .stream() to .parallelStream().
Alternatively, if you were trying to increment a variable manually, and use stream parallelisation, then you would want to use something like AtomicLong for thread safety. A simple variable, even if the compiler allowed it, would not be thread-safe.

To get both numbers, matching and non-matching elements, you can use
Map<Boolean, Long> result = map.keySet().stream()
.collect(Collectors.partitioningBy(k -> k.contains("FOO"), Collectors.counting()));
long fooCount = result.get(true);
long barCount = result.get(false);
But since your source is a Map, which knows its total size, and want to calculate a percentage, for which barCount is not needed, this specific task can be solved as
private int calculateFooPercentage() {
return (int)(map.keySet().stream().filter(k -> k.contains("FOO")).count()
*100/map.size());
}
Both variants are thread safe, i.e. changing stream() to parallelStream() will perform the operation in parallel, however, it’s unlikely that this operation will benefit from parallel processing. You would need humongous key strings or maps to get a benefit…

I agree with the other answers indicating you should use countor partitioningBy.
Just to explain the atomicity problem with an example, consider the following code:
private static AtomicInteger i1 = new AtomicInteger(0);
private static int i2 = 0;
public static void main(String[] args) {
IntStream.range(0, 100000).parallel().forEach(n -> i1.incrementAndGet());
System.out.println(i1);
IntStream.range(0, 100000).parallel().forEach(n -> i2++);
System.out.println(i2);
}
This returns the expected result of 100000 for i1 but an indeterminate number less than that (between 50000 and 80000 in my test runs) for i2. The reason should be pretty obvious.

Related

Decrease two custom objects values using reduce operation

I have a custom object like the following one.
public class Count {
private int words;
private int characters;
//getters & setters && all args constructor
void Count decrease(Count other) {
this.words -= other.words;
this.characters -= other.characters;
return this;
}
}
I want to achieve the next result, e.g:
Count book1 = new Count(10, 35);
Count book2 = new Count(6, 10);
the result would be:
Count result = Count(4, 25) -> ([10-6], [35-10])
I tried this solution but it didn't work.
Stream.of(book1, book2).reduce(new CountData(), CountData::decrease)
It's possible to achieve this result using reduce operation or another stream operation from java >=8 ?
The following ad hoc solution uses a single-element stream of book2 to be subtracted from a seed initialized with book1 values:
Count reduced = Stream.of(book2)
.reduce(new Count(book1.getWords(), book1.getCharacters()), Count::decrease);
Here value of book1 is not affected.
However, for this specific case this can be done without Stream API:
Count reduced = new Count(book1.getWords(), book1.getCharacters())
.decrease(book2);

How can I use Java Stream to reduce with this class structure?

This is the example of the class I'm working on
public class TestReduce
{
private static Set<Integer> seed = ImmutableSet.of(1, 2);
public static void main(String args[]) {
List<Accumulator> accumulators = ImmutableList.of(new Accumulator(ImmutableSet.of(5, 6)), new Accumulator(ImmutableSet.of(7, 8)));
accumulators.stream()
.forEach(a -> {
seed = a.combineResult(seed);
});
System.out.println(seed);
}
}
class Accumulator
{
public Accumulator(Set<Integer> integers)
{
accumulatedNumbers = integers;
}
public Set<Integer> combineResult(Set<Integer> numbers) {
// Do some manipulation for the numbers
return (the new numbers);
}
private Set<Integer> accumulatedNumbers;
}
I would like to reduce all of the Accumulators to just a set of numbers but with the initial value. However, I cannot change the signature of the method combineResult. In the example, I did this by just using forEach but I'm not sure if there's a cleaner way or java stream way to achieve this? I tried using reduce but I couldn't quite get the arguments of the reduce right.
(Answer for the original question)
This doesn't seem like a good approach. You're just unioning some sets.
If you can't change the signature of combineResult, you can do:
ImmutableSet<Integer> seed =
Stream.concat(
initialSet.stream(),
accumulators.stream()
// Essentially just extracting the set from each accumulator.
// Adding a getter for the set to the Accumulator class would be clearer.
.map(a -> a.combineResult(Collections.emptySet()))
.flatMap(Set::stream))
.collect(ImmutableSet.toImmutableSet());
For a generalized combineResult, you shouldn't use reduce, because that operation may be non-associative.
It's easy just to use a plain old loop in that case.
Set<Integer> seed = ImmutableSet.of(1, 2);
for (Accumulator a : accumulators) {
seed = a.combineResult(seed);
}
This avoids the principal issue with your current approach, namely non-thread locality of the calculation state (that is, other threads and previous invocations of the loop cannot affect the current invocation).

Java 8 lambda sum, count and group by

Select sum(paidAmount), count(paidAmount), classificationName,
From tableA
Group by classificationName;
How can i do this in Java 8 using streams and collectors?
Java8:
lineItemList.stream()
.collect(Collectors.groupingBy(Bucket::getBucketName,
Collectors.reducing(BigDecimal.ZERO,
Bucket::getPaidAmount,
BigDecimal::add)))
This gives me sum and group by. But how can I also get count on the group name ?
Expectation is :
100, 2, classname1
50, 1, classname2
150, 3, classname3
Using an extended version of the Statistics class of this answer,
class Statistics {
int count;
BigDecimal sum;
Statistics(Bucket bucket) {
count = 1;
sum = bucket.getPaidAmount();
}
Statistics() {
count = 0;
sum = BigDecimal.ZERO;
}
void add(Bucket b) {
count++;
sum = sum.add(b.getPaidAmount());
}
Statistics merge(Statistics another) {
count += another.count;
sum = sum.add(another.sum);
return this;
}
}
you can use it in a Stream operation like
Map<String, Statistics> map = lineItemList.stream()
.collect(Collectors.groupingBy(Bucket::getBucketName,
Collector.of(Statistics::new, Statistics::add, Statistics::merge)));
this may have a small performance advantage, as it only creates one Statistics instance per group for a sequential evaluation. It even supports parallel evaluation, but you’d need a very large list with sufficiently large groups to get a benefit from parallel evaluation.
For a sequential evaluation, the operation is equivalent to
lineItemList.forEach(b ->
map.computeIfAbsent(b.getBucketName(), x -> new Statistics()).add(b));
whereas merging partial results after a parallel evaluation works closer to the example already given in the linked answer, i.e.
secondMap.forEach((key, value) -> firstMap.merge(key, value, Statistics::merge));
As you're using BigDecimal for the amounts (which is the correct approach, IMO), you can't make use of Collectors.summarizingDouble, which summarizes count, sum, average, min and max in one pass.
Alexis C. has already shown in his answer one way to do it with streams. Another way would be to write your own collector, as shown in Holger's answer.
Here I'll show another way. First let's create a container class with a helper method. Then, instead of using streams, I'll use common Map operations.
class Statistics {
int count;
BigDecimal sum;
Statistics(Bucket bucket) {
count = 1;
sum = bucket.getPaidAmount();
}
Statistics merge(Statistics another) {
count += another.count;
sum = sum.add(another.sum);
return this;
}
}
Now, you can make the grouping as follows:
Map<String, Statistics> result = new HashMap<>();
lineItemList.forEach(b ->
result.merge(b.getBucketName(), new Statistics(b), Statistics::merge));
This works by using the Map.merge method, whose docs say:
If the specified key is not already associated with a value or is associated with null, associates it with the given non-null value. Otherwise, replaces the associated value with the results of the given remapping function
You could reduce pairs where the keys would hold the sum and the values would hold the count:
Map<String, SimpleEntry<BigDecimal, Long>> map =
lineItemList.stream()
.collect(groupingBy(Bucket::getBucketName,
reducing(new SimpleEntry<>(BigDecimal.ZERO, 0L),
b -> new SimpleEntry<>(b.getPaidAmount(), 1L),
(v1, v2) -> new SimpleEntry<>(v1.getKey().add(v2.getKey()), v1.getValue() + v2.getValue()))));
although Collectors.toMap looks cleaner:
Map<String, SimpleEntry<BigDecimal, Long>> map =
lineItemList.stream()
.collect(toMap(Bucket::getBucketName,
b -> new SimpleEntry<>(b.getPaidAmount(), 1L),
(v1, v2) -> new SimpleEntry<>(v1.getKey().add(v2.getKey()), v1.getValue() + v2.getValue())));

Calculate SumOfInts from Iterable

I need to calculate sum of Iterable<Integer>.
final Iterable<Integer> score = this.points()
final sum = new SumOfInts(...).value()
How can I do this using class SumOfInts?
Since Cactoos 0.22 you can:
int sum = new SumOf(1, 2, 3).intValue();
If I understand your question correctly, you could sum the iterable using code similar to this example:;
final Iterable<Integer> score = Arrays.asList(1, 2, 3, 4);
Optional<Integer> sum = StreamSupport.stream(score.spliterator(), false).reduce((i1, i2) -> i1 + i2);
System.out.println(sum.get());
The printed result is:
10
Explanation:
Iterable can be converted to a spliterator and spliterator to stream. You can then perform reductions on the stream.
As soon as you have the stream you can solve the reduction in multiple ways.
Another alternative:
int summed = StreamSupport.stream(score.spliterator(), false).mapToInt(Integer::intValue).sum();
System.out.println(summed);
This is perhaps nicer, as you get rid of the Optional result.
I couldn't find any examples of how to convert Integer to Scalar<Number> but since the Scalar is an interface you can do something like this.
public long sum() throws Exception {
final List<Scalar<Number>> score = this.points()
.stream()
.map(this::toScalar)
.collect(Collectors.toList());
return new SumOfInts(score).value();
}
private Scalar<Number> toScalar(final Number number) {
return () -> number;
}
But I bet there is a better way.

Java 8 Nested ParallelStream not working Properly

package com.spse.pricing.client.main;
import java.util.stream.IntStream;
public class NestedParalleStream {
int total = 0;
public static void main(String[] args) {
NestedParalleStream nestedParalleStream = new NestedParalleStream();
nestedParalleStream.test();
}
void test(){
try{
IntStream stream1 = IntStream.range(0, 2);
stream1.parallel().forEach(a ->{
IntStream stream2 = IntStream.range(0, 2);
stream2.parallel().forEach(b ->{
IntStream stream3 = IntStream.range(0, 2);
stream3.parallel().forEach(c ->{
//2 * 2 * 2 = 8;
total ++;
});
});
});
//It should display 8
System.out.println(total);
}catch(Exception e){
e.printStackTrace();
}
}
}
Pls help how to customize parallestream to make sure we will get consistency results.
Since multiple threads are incrementing total, you must declare it volatile to avoid race conditions
Edit: volatile makes read / write operations atomic, but total++ requires mores than one operation. For that reason, you should use an AtomicInteger:
AtomicInteger total = new AtomicInteger();
...
total.incrementAndGet();
Problem in statement total ++; it is invoked in multiple threads simultaneously.
You should protect it with synchronized or use AtomicInteger
LongAdder or LongAccumulator are preferable to AtomicLong or AtomicInteger where multiple threads are mutating the value and it's intended to be read relatively few times, such as once at the end of the computation. The adder/accumulator objects avoid contention problems that can occur with the atomic objects. (There are corresponding adder/accumulator objects for double values.)
There is usually a way to rewrite accumulations using reduce() or collect(). These are often preferable, especially if the value being accumulated (or collected) isn't a long or a double.
There is a major problem regarding mutability with the way you are solving it. A better way to solve it the way you want would be as follows:
int total = IntStream.range(0,2)
.parallel()
.map(i -> {
return IntStream.range(0,2)
.map(j -> {
return IntStream.range(0,2)
.map(k -> i * j * k)
.reduce(0,(acc, val) -> acc + 1);
}).sum();
}).sum();

Categories

Resources