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);
Related
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;
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.
I have the following list of double values:
items {9.0, 4.0, 16.0, -6.0, 5.0}
I want to find the maximum and minimum values and for that I did:
double max = items.stream().max(Comparator.comparing(String::valueOf)).get();
double min = items.stream().min(Comparator.comparing(String::valueOf)).get();
The result that I got is max=9.0 and min=-6.0. I was expecting the maximum to be 16.0. Later, I changed 16.0 to 92.0 and it worked; it gave me max=92.0
Do you know how to solve that?
You don't want to compare using strings but by the natural order of your double elements, i.e. Comparator.naturalOrder() instead of Comparator.comparing(String::valueOf).
Comparing via strings will result in the characters being compared and since the character value of 9 (of "9.0") is greater than 1 (of "16.0") you get the result you see. Changing "16.0" to "92.0" will result in . to be compared with 2 (since the first character is equal) and thus "92xx" is greater than "9.xx".
What about :
double max = items.stream().mapToDouble(Double::doubleValue).max().getAsDouble();//16.0
double min = items.stream().mapToDouble(Double::doubleValue).min().getAsDouble();//-6.0
It appears you want to compare the number numerically instead of their String representation e.g. "16.0" < "9.0" as '1' < '9'
List<Double> items = Arrays.asList(9.0, 4.0, 16.0, -6.0, 5.0);
double max = items.stream().max(Comparator.naturalOrder()).get();
double min = items.stream().min(Comparator.naturalOrder()).get();
System.out.println(min + " " + max);
prints
-6.0 16.0
There's a more appropriate stream type for doubles. Using it, you can get min and max in one terminal operation (eliminating the need to supply a comparator in the process):
DoubleSummaryStatistics stats = items.stream().mapToDouble(d -> d)
.summaryStatistics();
//even better: DoubleStream.of(9.0, 4.0, 16.0, -6.0, 5.0).summaryStatistics()
And stats will have:
count=5, sum=28.000000, min=-6.000000, average=5.600000, max=16.000000
Your are comparing them as String so by alphabetical order : 1xx is before 9xx
You need to compare then as Double or Integer, so use Comparator.comparing(Double::valueOf) (or Integer::valueOf)
items
.stream()
.sorted()
.limit(1)
.collect(Colletors.toList();
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.
i wanted to try out some of the functionality of lambdas and wanted to write filter an ArrayList and use the methods of IntStream to calculate the average and maximum of an ArrayList of numbers
My first thought was to just filter the ArrayList, save the stream and then use the methods to calculate:
ArrayList<Integer> arr = new ArrayList<>();
arr.add(5);
arr.add(7);
arr.add(11);
IntStream s = arr.stream().filter(i -> i < 10).mapToInt(i -> (int)i);
int maxBelowTen = s.max().getAsInt();
double avgBelowTen = s.average().getAsDouble();
System.out.println("Maximum below ten: " + maxBelowTen);
System.out.println("Average below ten: " + avgBelowTen);
However, this throws an java.lang.IllegalStateException: stream has already been operated upon or closed
With this information, i brought it to work of course, by opening two streams and filtering twice
int maxBelowTen = arr.stream().filter(i -> i < 10).mapToInt(i -> (int) i).max().getAsInt();
double avgBelowTen = arr.stream().filter(i -> i < 10).mapToInt(i -> (int) i).average().getAsDouble();
But my question now is about performance. Isn't that pretty slow, if i have to filter and map the stream twice. Why can't I operate more than once on a stream, i fail to understand why they implemented it this way.
Wouldn't it be possible to leave a stream open after an operation, because every operator method returns a new stream or a single value.
What is the reason for this, or am I just using it wrong?
If you did it the good old way, by using a loop, you would compute the max element and the average using a single loop. So you just need to do the same thing here. And fortunately, the Stream API can do it for you :
IntStream s = arr.stream().mapToInt(i -> Integer::intValue).filter(i < 10);
IntSummaryStatistics stats = s.summaryStatistics();
double average = stats.getAverage();
int max = stats.getMax();
Reading the javadoc of IntSummaryStatistics should help you understand how you could implement such an operation by yourself.
If you want to "save" the result of intermediate stream operations, you can do that; you just have to do it explicitly: you'd have to do arr.stream().filter(i -> i < 10).mapToInt(i -> (int) i).toArray(), store that int[], and then do the operations on that.
Streams are not a data structure, they're a pending computation that hasn't been run yet, and might merge future operations together in nonstandard ways. If you want to store intermediate results in a proper data structure, you have to do it yourself.