6

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?

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
Sionnach733
  • 4,686
  • 4
  • 36
  • 51
  • 1
    I think this problem is addressed (and hopefully solved) on this thread http://stackoverflow.com/questions/19803058/java-8-stream-getting-head-and-tail – luanjot May 16 '14 at 15:38

3 Answers3

6

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}
Alexis C.
  • 91,686
  • 21
  • 171
  • 177
5

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>.

skiwi
  • 66,971
  • 31
  • 131
  • 216
0

You can collect several results in a single iteration, if you want to get two outputs. In your case, it might look as follows:

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

int[] counts = randomStream.collect(
    () -> new int[] { 0, 0 }, // supplier
    (a, v) -> a[v]++, // accumulator
    (l, r) -> { l[0] += r[0]; l[1] += r[1]; }); // combiner

System.out.println("Heads: " + counts[0]);
System.out.println("Tails: " + counts[1]);
nosid
  • 48,932
  • 13
  • 112
  • 139