O'Niel
O'Niel

Reputation: 1763

Stream generate until a limit but only matching a predicate

How do I generate a list using Stream of exactly 100 elements. But all elements in the list should match a predicate.

Testcode:



import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Predicate;
import java.util.stream.Stream;


public class Main {
    // My predicate
    public static Predicate<Integer> isBig() {
        return p -> p > 10;
    }

    public static void main(String args[]) {
        List<Integer> list = new ArrayList<>();

        Stream.generate(new Random()::nextInt)
                .limit(100)
                //???????
                .forEach(a -> list.add(a));

        // Always needs to be 100
        System.out.println(list.size());
    }
}

I could do this with a while loop and a counter (while counter < 100). In the while loop I then would create random Integers, and only add the to a specific list and increase the counter when an element matches the predicate.

But this is not efficient and it needs to be done with streams.

PseudoCode:

 Stream.generate(new Random()::nextInt)
                .limit(100)
                .onlyAddIfMatches(isBig())
                .forEach(a -> list.add(a));

How can this be done?

Upvotes: 0

Views: 943

Answers (2)

Ackdari
Ackdari

Reputation: 3498

Edit:

Do not use the filter method on a stream with a pseudo-random-number source if you can do what ever you want to do differently.

If you do you will at best run into performance drops and at worst into a hanging program.

This is because if you just take random numbers and filter them you will most probably gennerate more numbers than you need. For example if you want 100 even random numbers you will on average need to generate 200 random numbers.

The worst case comes into play if the PRNG has a bad seed and cycles only through numbers that does not match the predicate, so you can never collect the requiered amount of numbers.

To conclude: If you need n random numbers that meet a certain predicate (for example positive) the (if possible) just create n random numbers and map the ones that do not match the predicate to one that matches (for example take the absolute value of the negative numbers to only have positive numbers).

Original answer

If you want to filter a stream just use the method filter: like

Random r = new Random();
List<Integer> list = Stream.generate(() -> r.nextInt())
    .filter(n -> n > 0) // filter for positive numbers
    .filter(n -> n % 2 == 0) // filter for even
    .limit(10)
    .collect(Collectors.toList());

But you should be cousious about your stream source. If you use a PRNG you should try to limit the range of numbers that are generated.

For example the above example can be achived with

Random r = new Random();
List<Integer> list = Stream.generate(() -> r.nextInt(Integer.MAX_VALUE/2) * 2)
    .limit(10)
    .collect(Collectors.toList());

and is to be preferred, because the first example could run into a cycle of pseudo-random-numbers that never meet the requirements.

So as a general rule. If you deal with a random source, try to create from the input the numbers you want and not filter the random input.

Upvotes: 4

Tashkhisi
Tashkhisi

Reputation: 2252

Like this:

List<Integer> list = Stream.generate(new Random()::nextInt)
                .filter(p -> p > 10)
                .limit(100).collect(Collectors.toList());

Or like this one:

public class Main {

    private static boolean isBig(Integer input) {
        return input > 10;
    }

    public static void main(String args[]) {
        List<Integer> list = Stream.generate(new Random()::nextInt)
                .filter(Main::isBig)
                .limit(100).collect(Collectors.toList());
    }
}

Upvotes: 3

Related Questions