Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting two different outputs from a Stream

I am testing out the new Stream API in java-8 and want to check the outcome of 10000 random coinflips. So far I have:

    public static void main(String[] args) {

        Random r = new Random();
        IntStream randomStream = r.ints(10000,0, 2);

        System.out.println("Heads: " + randomStream.filter(x -> x==1).count());
        System.out.println("Tails: " + randomStream.filter(x -> x==0).count());
    }

but this throws the exception:

 java.lang.IllegalStateException: stream has already been operated upon or closed

I understand why this is happenning but how can i print the count for heads and tails if I can only use the stream once?

like image 466
Sionnach733 Avatar asked May 16 '14 15:05

Sionnach733


People also ask

How do I concatenate two streams?

Stream. concat() method creates a concatenated stream in which the elements are all the elements of the first stream followed by all the elements of the second stream. The resulting stream is ordered if both of the input streams are ordered, and parallel if either of the input streams is parallel. The calls to Stream.

How many types of output streams are there?

An output stream object is a destination for bytes. The three most important output stream classes are ostream , ofstream , and ostringstream .

Can we use same stream twice?

A stream should be operated on (invoking an intermediate or terminal stream operation) only once. A stream implementation may throw IllegalStateException if it detects that the stream is being reused. So the answer is no, streams are not meant to be reused.

Can you merge streams?

3.1. Merging Streams. Since it is an instance method, we can easily chain it and append multiple streams. Note that we could also create a List out of the stream by using toList() if we type the resultingStream variable to the StreamEx type.


2 Answers

This first solution is relying on the fact that counting the number of heads and tails of 10 000 coinflips follows a binomial law.

For this particular use case, you can use the summaryStatistics method.

Random r = new Random();
IntStream randomStream = r.ints(10000,0, 2);
IntSummaryStatistics stats =  randomStream.summaryStatistics();
System.out.println("Heads: "+ stats.getSum());
System.out.println("Tails: "+(stats.getCount()-stats.getSum()));


Otherwise you can use the collect operation to create a map which will map each possible result with its number of occurences in the stream.
Map<Integer, Integer> map = randomStream
                            .collect(HashMap::new, 
                                     (m, key) -> m.merge(key, 1, Integer::sum), 
                                     Map::putAll);
System.out.println(map); //{0=4976, 1=5024}

The advantage of the last solution is that this works for any bounds you give for the random integers you want to generate.

Example:

IntStream randomStream = r.ints(10000,0, 5);
....
map => {0=1991, 1=1961, 2=2048, 3=1985, 4=2015}
like image 192
Alexis C. Avatar answered Oct 11 '22 14:10

Alexis C.


While all other answers are correct, they are formulated a bit cumbersome.

Map<Integer, Long>, maps the flipped coin to a count.

Map<Integer, Long> coinCount = new Random().ints(10000, 0, 2)
        .boxed()
        .collect(Collectors.groupingBy(i -> i, Collectors.counting()));

This will first create the IntStream, then box them to an Stream<Integer>, as you will be storing them in their boxed version anyhow in this example. And lastly collect them with a groupingBy function on the identity i -> i, which gives you a Map<Integer, List<Integer>>, which is not what you want, hence you replace the List<Integer> with the operation Collectors.counting() on it, such that the List<Integer> becomes a Long, hence resulting in a Map<Integer, Long>.

like image 30
skiwi Avatar answered Oct 11 '22 12:10

skiwi