'java stream list of filters

Let's say I've got a list of Strings and I want to filter them by the list of filtering Strings. For the list which consists of: "abcd", "xcfg", "dfbf"

I would precise list of filtering Strings: "a", "b", and after something like filter(i->i.contains(filterStrings) I would like to receive list of "abcd", "dfbf", And for a list of filtering Strings: "c", "f" I would like to reveive list of "xcfg" and "dfbf".

List<String> filteredStrings = filteredStrings.stream()
            .filter(i -> i.contains("c") || i.contains("f")) //i want to pass a list of filters here
            .collect(Collectors.toList());

Is there an other way of doing this instead of expanding body of lambda expression and writing a function with a flag which will check every filter?



Solution 1:[1]

You should instead be performing a anyMatch over the list to match from:

List<String> input = Arrays.asList("abcd", "xcfg", "dfbf"); // your input list
Set<String> match = new HashSet<>(Arrays.asList("c", "f")); // to match from
List<String> filteredStrings = input.stream()
        .filter(o -> match.stream().anyMatch(o::contains))
        .collect(Collectors.toList());

Solution 2:[2]

You can create a List, that has an arbitrary number of filters

List<Predicate<String>> filterList = new ArrayList<>();
Predicate<String> containsCPredicate = s -> s.contains("c");
filterList.add(containsCPredicate);
filterList.add(s -> s.contains("f"));
...

You can then reduce the list of these filters into one, combining them:

filterList.stream().reduce(i -> true, Predicate::and)

when applying them on your stream pipeline, like

List<String> filteredStrings = strings.stream()
        .filter(filterList.stream().reduce(i -> true, Predicate::and))
        .collect(Collectors.toList());

The initial i -> true Predicate is the identity, the reduce operation needs as the first parameter.

Solution 3:[3]

You can change your contains with a simple regex :

.filter(i -> i.matches(".*[cf].*")) // to check just one character

or :

.filter(i -> i.matches(".*(c|f).*")) // or if you have a words

Solution 4:[4]

A filter can be represented as a Predicate. In your case a Predicate<String>. So, a list of filter can be stored in a List<Predicate<String>>.

Now, if you want to apply such a list on each element of your Stream:

List<String> filteredStrings = input.stream()
                                    .filter(i -> filterList.stream().anyMatch(f -> f.test(i))) 
                                    .collect(Collectors.toList());

To complete the example:

List<String> input = new ArrayList<>(Arrays.asList ("abcd", "xcfg", "dfbf","erk"));
List<Predicate<String>> filterList = new ArrayList<>();
filterList.add (i -> i.contains("c"));
filterList.add (i -> i.contains("f"));
List<String> filteredStrings = input.stream()
                                    .filter(i -> filterList.stream().anyMatch(f -> f.test(i))) 
                                    .collect(Collectors.toList());
System.out.println (filteredStrings);

Output:

[abcd, xcfg, dfbf]

Solution 5:[5]

You can use a Pattern:

static final Pattern pattern = Pattern.compile("c|f");

And then check if a string matches said pattern.

List<String> strings = filteredStrings.stream()
    .filter(s -> pattern.matcher(s).find())
    .collect(Collectors.toList());

That pattern can of course be computed by a given input:

public static Pattern compute(String... words) {
    StringBuilder pattern = new StringBuilder();
    for(int i = words.length - 1; i >= 0; i++) {
       pattern.append(words[i]);
       if(i != 0) {
           pattern.append('|');
       }
    }
    return Pattern.compile(pattern);
}

Which could then be called like this:

Pattern patten = compute("some", "words", "hello", "world");

Which would result in a regex:

some|words|hello|world

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 guest
Solution 4
Solution 5 Lino