5

I want to make a stream with random numbers. As soon as the numbers fullfill a certain condition I want to now how many iterations was needed. So either I want to have the size of the stream or an Collection from which I can read then the size.

Here are my approaches:

random.ints(0, Integer.MAX_VALUE).anyMatch(a -> {return a < 20000;});

This gives me only the a boolean as soon as my condition is fullfilled.

random.ints(0, Integer.MAX_VALUE).filter(a -> a < 20000).limit(1).count();

And this returns obviously 1. But I want to have the size before I filtered my result. I further tried several things with a counting variable but since lambdas are capturing them effectifely final from outside I have an initialising problem.

Any help or hint is appreciated

dbmongo
  • 85
  • 6
  • 1
    you can use `AtomicInteger` for initialisation, peek in stream to increment counter, but it won't work the same in parallel stream – Saravana Dec 18 '17 at 12:54
  • @Saravana but how to quit out of the stream? – Lino Dec 18 '17 at 12:56
  • 1
    @Lino we quit when `anyMatch` returns true, `peek` would be done before `anyMatch` to have the `count` – Saravana Dec 18 '17 at 12:58
  • 1
    @Saravana that sounds like an answer to me ;) – Lino Dec 18 '17 at 12:59
  • 2
    No, streams in Java 8 aren't designed for this use case. Using `AtomicInteger` and `peek` as a counter is a hack and should be avoided. Streams were designed to avoid side-effects and this approach goes against this core principle. Unless streams are an absolute necessity, you're better off switching to Java 9 or using a traditional `for-each-index` loop. – Sync Dec 18 '17 at 13:19
  • 1
    @Synch of course it does, but if you really need it? I can't say that the OP does or not - but in case he/she has a use case, there is no other way – Eugene Dec 18 '17 at 13:20
  • @Eugene: there is no guaranty that `peek` only reports elements up to the first match. While that’s very likely in a sequential execution, it’s also very likely not the case in a parallel execution. As explained [here](https://stackoverflow.com/a/33636377/2711488), `peek` allows to perform an action when elements are *processed*, but it does not bear any “before a certain other action” semantic. – Holger Dec 19 '17 at 09:50
  • @Holger agreed. So, if I wanted to fo that the only way would be a custom spliterator? I would hate that btw... – Eugene Dec 19 '17 at 10:19
  • @Eugene: that’s truly a task unsuitable for the Stream API. If you still want to do it with a similar API, then yes, you have to implement it yourself with the additional guarantees. However, that still won’t beat a simple `while(random.nextInt()>20000 && count – Holger Dec 19 '17 at 10:43

3 Answers3

3

Java 9 has a feature to support that - takeWhile:

random.ints(0, Integer.MAX_VALUE).takeWhile(a -> a < 20000).count();
Eran
  • 387,369
  • 54
  • 702
  • 768
  • 1
    that's not really what the OP wants, he wants to know how many iterations where made to fulfill a certain requirement – Eugene Dec 18 '17 at 12:48
  • 1
    @Eugene that's what above does, doesn't it? The count determines the iteration – Lino Dec 18 '17 at 12:49
  • 2
    @Eugene that's what `count` would give (when combined with `takeWhile`). – Eran Dec 18 '17 at 12:49
  • Is there a java 8 solution or is it simple impossible to make this with streams in java 8? – dbmongo Dec 18 '17 at 12:50
  • I don't think there's a Java 8 solution. For Java 8, the old-skool `for-each by index` with an `if condition true then break` might be the most suitable approach. – Sync Dec 18 '17 at 12:52
  • @dbmongo i think there is no way to skip out of a stream than with exception – Lino Dec 18 '17 at 12:52
  • @dbmongo You can probably use `reduce` to count how many stream elements are consumed until a condition is met for the first time, but I think you will have to break out of the stream pipeline with an exception. – Eran Dec 18 '17 at 12:53
  • 2
    @Eran count here would be the same value as how many elements where produced by the stream, my understanding was that OP wants something like: take elements that are higher than 10 and limit these to 100 - how many elements from the stream (`generate` where produced) in order to fulfill this – Eugene Dec 18 '17 at 13:17
  • @Eran I dont understand how reduce can help me to count the elements. Can you give a small example? – dbmongo Dec 18 '17 at 13:36
  • 1
    Looks good on the first glance and likely does the expected thing in a sequential execution, but the problem is that the stream of random ints is *unordered*, so the following applies: [If this stream is unordered, and some (but not all) elements of this stream match the given predicate, then the behavior of this operation is nondeterministic; it is free to take any subset of matching elements (which includes the empty set).](https://docs.oracle.com/javase/9/docs/api/java/util/stream/Stream.html#takeWhile-java.util.function.Predicate-) – Holger Dec 19 '17 at 09:42
  • @Holger Since the stream is generated as if calling `nextInt()` as long as condition isn't matched this gives me always the correct subset. I think the scentence from the JavaDoc you mentioned applies for finit unordered sets. It means that elements after the first match are no more concerned. This doesnt make any sense for infinite streams. (I completely agree that this isn't thread safety) – dbmongo Dec 19 '17 at 12:22
  • @dbmongo: no, the fact that the stream is *unordered* implies, that you can *not* assume that it behaves “as if calling `nextInt()` as long as …”. Obviously, that’s the straight-forward way to implement this operation for a sequential stream, still, the specification says nowhere that you can rely on this. And since the stream is *unordered*, there is no “before the match” or “after the match”. That’s the whole point, “before” or “after” are meaningless for random data. This has nothing to do with whether the stream is infinite, the term “infinite” does not appear anywhere in the cited text. – Holger Dec 19 '17 at 12:32
0

You can try takeWhile from this project

Example :

IntStream intStream = new Random().ints();
        Stream<Integer> result = StreamUtils.takeWhile(intStream.boxed().peek(i->System.out.println("in stream : " + i)), i -> i > 2000);
        long count = result.peek(i->System.out.println("in result : " + i))
                .count();
        System.out.println(count);

Prints:

in stream : 1466689339
in result : 1466689339
in stream : 1588320574
in result : 1588320574
in stream : 1621482181
in result : 1621482181
in stream : -2140739741
3
Alexander.Furer
  • 1,817
  • 1
  • 16
  • 24
  • Can you give the implementation of `takeWhile` instead of just linking to it? I must admit you do give an example how to use it so it is not strickly a link-only answer. – Kasper van den Berg Dec 18 '17 at 15:15
  • 1
    What is the point to copy-paste the implementation ? It's easy to see it here https://github.com/poetix/protonpack/blob/master/src/main/java/com/codepoetics/protonpack/TakeWhileSpliterator.java – Alexander.Furer Dec 18 '17 at 15:20
0

There is no Stream.takeWhile() in Java 8, but you can easily workaround this with iterator():

public static int countWhile(IntStream stream, IntPredicate predicate) {
    PrimitiveIterator.OfInt iterator = stream.iterator();
    int i = 0;
    while (predicate.test(iterator.nextInt())) {
        i++;
    }
    return i;
}
ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155