Manipulate output in java8 stream - java

Imagine we have list of 3 objects with minutes field as values: 5,5,7, 8
int sumOfFields = found.stream()
.filter(abc -> minutesLessThan5(abc.getMinutes())))
.mapToInt(abc::getMinutes)
.sum();
// will return 10
But how can I change my output
e.g. instead of getMinutes I want to return my own value e.g. 40
int sumOfFields = found.stream()
.filter(abc -> minutesLessThan5(abc.getMinutes())))
.mapToInt(abc ->abc.getMinutes() = 40) //this is pseudo code what I try to achive
.sum();
// output should be 80.

Not really sure why people didn't made an answer to this, yet as pointed out in comments, you can follow either of the approach
int sumOfFields = found.stream()
.filter(abc -> minutesLessThan5(abc.getMinutes())))
.mapToInt(abc -> 40) // map value to be returned as 40
.sum();
or instead since you are replacing all such values with a constant value 40, you can also make use of the count() and multiply that with the constant value.
int sumOfFields = (int) found.stream() // casting from long to int
.filter(abc -> minutesLessThan5(abc.getMinutes())))
.count() * 40;

Related

Iterate list of objects and get average

Is there any way to get an average here via one iteration? I can do it with regular "For loop" but want to use stream instead.
final Double ratingSum = ratingCount.stream().mapToDouble(RecommendRatingCount::getRatingSum).sum();
final Double countSum = ratingCount.stream().mapToDouble(RecommendRatingCount::getCount).sum();
return ratingSum /countSum;
Assuming Java 12 or higher is used a teeing collector
return
ratingCount.stream()
.collect(Collectors.teeing(
Collectors.summingDouble(RecommendRatingCount::getRatingSum),
Collectors.summingDouble(RecommendRatingCount::getCount),
(sum, count) -> sum / count));
Decompose each object into separate ratings, each value being rating/count, by first expanding out each object count times, then converting each to its discounted value, then summarise all such values:
double average = ratingCount.stream()
.flatMap(rrc -> generate(() -> rrc).limit(rrc.getCount()))
.mapToDouble(rcc -> rcc.getRatingSum() / rcc.getCount())
.summaryStatistics().getAverage();
Assuming your RatingCount is a natural number.
return ratingCount.stream()
.flatMapToDouble(a -> DoubleStream.concat(DoubleStream.of(a.getRatingSum()),
DoubleStream.generate(() -> 0).limit((long) a.getCount() - 1)))
.average().orElse(0);

I don't understand how to use lambda expressions

I'm trying to get a lambda expression to count the amount of odd numbers in a small collection of integers. In general I'm just confused about the syntax and the types I can use with them. So far I have:
Collection<Integer> col = Arrays.asList(1, 2, 3, 4);
int count = 0;
count = col.forEach((Integer n) -> { if ((n % 2) != 0) count++;});
It says it can't convert from void to int, no idea why that's the error that comes up.
You can't modify local variables from within a lambda. You can use a mutable reference, but it would be much simpler to go with streams:
long count = col.stream().filter(n -> n % 2 != 0).count();
The variable count has type int. The lambda expression with forEach has type void, it's just like running the for loop, it doesn't return anything. So, your assignment
count = col.forEach(...)
is actually trying to assign void to int.

Count Elements in a stream and return Integer insted of long

I need to count Elements in a Stream and assign it to an Integer without casting.
.count() does return long
thought about the .collect(Collectors.reducing(..)) but cant figure it out.
I feel like there is something simple I don't get.
My Try:
Stream<String> s = Stream.of("Hallo ", "Test", "String");
Integer count = s.filter(e -> (e.length() >= lb && e.length() <= ub && !e.contains(" ")))
.map(e -> e.toUpperCase())
.distinct()
.collect(Collectors.reducing(0, e -> 1, Integer::sum)));
System.out.println(count);
Simply: don't.
Don't cast, but also don't make things overly complicated.
Rather look into safe ways of getting that int out of the long returned by count(). See here for starters:
int bar = Math.toIntExact(someLong);
for example. When you are 100% sure that the computed value always fits within int, then you just avoid putting down the catch for the potentially thrown ArithmeticException. And you still got that good feeling that you can't "overrun" without noticing.
But as said: don't invest time/energy into specially computing your own stuff, when you can use built-in functionality to count things, and turn them into int/Integer. Remember: each character you put into code needs to be read and understood later on. Thus even "20 characters" more add up over time. So when you always lean towards the shorter solution, as long as they are easy to read/understand.
Here is the right way. Convert all the distinct values to 1 using Stream::mapToInt - it produces the IntStream which has sum/count methods able to handle stream of numeric values directly without mapping:
Integer count = s.filter(e -> (e.length() >= lb && e.length() <= ub && !e.contains(" ")))
.map(String::toUpperCase)
.distinct()
.mapToInt(i -> 1)
.sum();
Without mapping to int, you can use Stream::reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner) to get the very same result:
Integer count = s.filter(e -> (e.length() >= 2 && e.length() <= 10 && !e.contains(" ")))
.map(String::toUpperCase)
.distinct()
.reduce(0, (a,b) -> a + 1, (a,b) -> a + b);
The interface of this method is little bit complicated:
U identity is set to 0 - a start of counting
accumulator ((a,b) -> a + 1) converts the String to int, each String will be converted to 1 and added to the previous result (0+1+1+1...).
combiner combines two consecutive values ((a,b) -> a + b) - the sum of the 1 values, which is practically the count.
If you want to count the elements in stream without using the build in .count() method then you could map each element to an int and reduce by summing them. Something like this:
Integer count = s.mapToInt(i -> 1).reduce((a, b) -> a + b).orElse(0);
Or as #Holger commented bellow to use the sum() after mapping.
Integer count = s.mapToInt(i -> 1).sum();
With Java 8, you can use Math.toIntExact(long val).
public static int toIntExact(long value)
Returns the value of the long argument; throwing an exception if the
value overflows an int.

Passing a non-associative function to reduce

My program has this line:
Function<String, Integer> f = (String s) -> s.chars().reduce(0, (a, b) -> 2 * a + b);
The function being passed to reduce is not associative. Reduce's documentation says that the function passed must be associative.
How can I rewrite this as an expression which doesn't break reduce's contract?
Under the current implementation and IFF you are not going to use parallel - you are safe with what you have right now. Obviously if you are OK with these disclaimers.
Or you can obviously create the function with a for loop:
Function<String, Integer> f = s -> {
int first = s.charAt(0) * 2 + s.charAt(1);
int total = first;
for (int x = 1; x < s.length() - 1; x++) {
total = total * 2 + s.charAt(x + 1);
}
return total;
};
You can convert this function to an associative function, as explained in this answer at the example of List.hashCode(). The difference lies only in the factor (2vs.31) and the start value (1vs.0).
It can be adapted to your task, which is especially easy when you have a random access input like a String:
Function<String, Integer> f =
s -> IntStream.range(0, s.length()).map(i -> s.charAt(i)<<(s.length()-i-1)).sum();
This would even run in parallel, but it’s unlikely that you ever encounter such humongous strings that a parallel evaluation provides a benefit. So what remains, is that most people might consider this solution less readable than a simple for loop…
Note that the above solution exhibits a different overflow behavior, i.e. if the String has more than 32 chars, due to the usage of the shift operator rather than multiplying with two.
The fix for this issue makes the solution even more efficient:
Function<String, Integer> f = s ->
IntStream.range(Math.max(0, s.length()-32), s.length())
.map(i -> s.charAt(i)<<(s.length()-i-1)).sum();
If the string has more than 32 chars, it only processes the last 32 chars, which is already sufficient to calculate the same result as your original function.

Java 8 Stream and operation on arrays

I have just discovered the new Java 8 stream capabilities. Coming from Python, I was wondering if there was now a neat way to do operations on arrays like summing, multiplying two arrays in a "one line pythonic" way ?
Thanks
There are new methods added to java.util.Arrays to convert an array into a Java 8 stream which can then be used for summing etc.
int sum = Arrays.stream(myIntArray).sum();
Multiplying two arrays is a little more difficult because I can't think of a way to get the value AND the index at the same time as a Stream operation. This means you probably have to stream over the indexes of the array.
//in this example a[] and b[] are same length
int[] a = ...
int[] b = ...
int[] result = new int[a.length];
IntStream.range(0, a.length).forEach(i -> result[i] = a[i] * b[i]);
Commenter #Holger points out you can use the map method instead of forEach like this:
int[] result = IntStream.range(0, a.length).map(i -> a[i] * b[i]).toArray();
You can turn an array into a stream by using Arrays.stream():
int[] ns = new int[] {1,2,3,4,5};
Arrays.stream(ns);
Once you've got your stream, you can use any of the methods described in the documentation, like sum() or whatever. You can map or filter like in Python by calling the relevant stream methods with a Lambda function:
Arrays.stream(ns).map(n -> n * 2);
Arrays.stream(ns).filter(n -> n % 4 == 0);
Once you're done modifying your stream, you then call toArray() to convert it back into an array to use elsewhere:
int[] ns = new int[] {1,2,3,4,5};
int[] ms = Arrays.stream(ns).map(n -> n * 2).filter(n -> n % 4 == 0).toArray();
Be careful if you have to deal with large numbers.
int[] arr = new int[]{Integer.MIN_VALUE, Integer.MIN_VALUE};
long sum = Arrays.stream(arr).sum(); // Wrong: sum == 0
The sum above is not 2 * Integer.MIN_VALUE.
You need to do this in this case.
long sum = Arrays.stream(arr).mapToLong(Long::valueOf).sum(); // Correct
Please note that Arrays.stream(arr) create a LongStream (or IntStream, ...) instead of Stream so the map function cannot be used to modify the type. This is why .mapToLong, mapToObject, ... functions are provided.
Take a look at why-cant-i-map-integers-to-strings-when-streaming-from-an-array

Categories