2

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);
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
Danny Bergs
  • 91
  • 1
  • 2
  • 8

4 Answers4

8

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.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • 1
    Hey thank you. I would do it as you said. But its an exam task.. so i need to do it. I think the prof want us to use some collect stuff in the stream itself. – Danny Bergs Aug 22 '18 at 13:41
  • 5
    OK, that is a valid requirement, that changes solution space! Then just go and accept on of the answers that does exactly that for you. But remember to tell your instructor that GhostCat told you: "dude, that is not what you should be doing"! – GhostCat Aug 22 '18 at 13:42
  • Im totally with you. Thank you – Danny Bergs Aug 22 '18 at 13:44
5

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.
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • Why do you filter distinct values? OP did not say anything about that :/ – dbl Aug 22 '18 at 13:43
  • 1
    @dbl just scroll the OP’s code example horizontally… – Holger Aug 22 '18 at 13:44
  • 1
    @dbl: See the code of OP, he uses `distinct()`. I use the very same code. – Nikolas Charalambidis Aug 22 '18 at 13:44
  • @DannyBergs could you edit the question then since what you are asking for and what you are expecting are two different things... :) – dbl Aug 22 '18 at 13:46
  • 1
    @Nikolas e -> e.toUpperCase() could be replaced with method reference. – dbl Aug 22 '18 at 13:49
  • 1
    Or `.collect(Collectors.reducing(0, e -> 1, Math::addExact))` which does not map to an `IntStream` but still avoids the boxing overhead *and* will through an exception when the number doesn’t fit into an `int`. – Holger Aug 22 '18 at 13:54
  • @Holger nice one :) Still i doubt that the professor will process such a big file just to check students' homework. – dbl Aug 22 '18 at 14:01
  • Just to add, instead of collecting and then use the external reduction method presented in Collectors isn't it better just to use s.map(e -> 1).reduce(0, Math::addExact); – dbl Aug 22 '18 at 14:03
  • 1
    @dbl teachers might be capable of analyzing the quality of a solution without having to try such a behavior, especially when they perhaps had exactly that in mind when saying “don’t cast”. Besides that, you can generate such a long stream using, e.g. `LongStream.range(-100, Integer.MAX_VALUE).mapToObj(Long::toHexString)`, though, in this specific use case, the *current implementation* will never produce too many elements after a `distinct()`. And yes, you can use `.map(e -> 1).reduce(0, Math::addExact);` but that’s just an inefficient variant of `.mapToInt(e -> 1).reduce(0, Math::addExact);`… – Holger Aug 22 '18 at 14:07
3

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();
dbl
  • 1,109
  • 9
  • 17
0

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.

NumeroUno
  • 1,100
  • 2
  • 14
  • 34