Pass parameters to Predicates
Asked Answered
S

2

15

I have the following map of the search criteria:

private final Map<String, Predicate> searchMap = new HashMap<>();

private void initSearchMap() {
    Predicate<Person> allDrivers = p -> p.getAge() >= 16;
    Predicate<Person> allDraftees = p -> p.getAge() >= 18
            && p.getAge() <= 25
            && p.getGender() == Gender.MALE;
    Predicate<Person> allPilots = p -> p.getAge() >= 23
            && p.getAge() <=65;

    searchMap.put("allDrivers", allDrivers);
    searchMap.put("allDraftees", allDraftees);
    searchMap.put("allPilots", allPilots);
}

I am using this map in the following way:

pl.stream()
    .filter(search.getCriteria("allPilots"))
    .forEach(p -> {
        p.printl(p.getPrintStyle("westernNameAgePhone"));
    });

I would like to know, how can I pass some parameters into the map of predicates?

I.e. I would like to get predicate from a map by its string abbreviation and insert a parameter into the taken out from a map predicate.

pl.stream()
    .filter(search.getCriteria("allPilots",45, 56))
    .forEach(p -> {
        p.printl(p.getPrintStyle("westernNameAgePhone"));
    });

Here is the link from I googled out this map-predicate approach.

Subfusc answered 23/8, 2015 at 12:7 Comment(6)
What you would like to do would not compile, and you didn't explain what it's supposed to do.Overcareful
I just gave an example. Sure, it wont compile. Here is what I would like to do: I would like to get a predicate from a map by its string abbreviation and insert a parameter into the taken out from a map predicate.Subfusc
What would the parameter be used for? What's the goal of 45 in your example?Overcareful
45 along with the 56 are the filtering parameters for a give predicate allPilots i.e. this predicate should return false if the pilot age is not in the age range (45, 65).Subfusc
OK. But what would this parameter mean if you passed "allDrivers" or "allDraftees" instead of "allPilots". You seem to want to contradictor things at the same time: store predicates in a map to be able to one with a string key without caring how it's implemented, and also be able to pass a parameter to customize the predicate, although they all do things differently.Overcareful
@JBNizet, yeah. I do understand that there a contradiction. But, I am currently concerned with the possibility to pass a parameters in such map.Subfusc
O
14

It seems that what you want is not to store a predicate in a Map. What you want is to be able to store something in a map that is able to create a Predicate<Person> from an int parameter. So what you want is something like this:

Map<String, IntFunction<Predicate<Person>>> searchMap = new HashMap<>();

You would fill it that way:

searchMap.put("allPilots", maxAge -> 
    (p -> p.getAge() >= 23
     && p.getAge() <= maxAge));

And you would use it like this:

Predicate<Person> allPilotsAgedLessThan45 = 
    searchMap.get("allPilots").apply(45);

Of course, it would be clearer if you created your own functional interface:

@FunctionalInterface
public MaxAgePersonPredicateFactory {
    Predicate<Person> limitToMaxAge(int maxAge);
}

You would still fill the map the same way, but you would then have slightly more readable code when using it:

Predicate<Person> allPilotsAgedLessThan45 = 
    searchMap.get("allPilots").limitToMaxAge(45);
Overcareful answered 23/8, 2015 at 13:9 Comment(3)
Exactly what I was looking for. Thank you!Subfusc
Given that the OP wants to pass in two int params, shouldn't this be a BiFunction<Integer, Integer, Predicate<Person>> instead of an IntFunction?Atypical
@Atypical I missed the second parameter in the question and only noticed the 45. But yes, if he needs two parameters, the signature of the factory will have to change.Overcareful
S
9

As I understand it, what you want to do is override some parameters in a named Predicate, but I think this will lead to confusion. If I'm asking for the predicate that determines if somebody is eligible to be a pilot, I don't want to have to worry about knowing what the requirements are at the time when I call it.

Generating predicates on the fly using a Map would be tricky - I'd think the nicest way would be to instead have a Map<String, PredicateFactory>, where the PredicateFactory is some sort of complicated class that generates predicates given some parameters:

Map<String, PredicateFactory<T>> predicates = new HashMap<>();

public Predicate<T> get(String name, Object... params) {
    return predicates.get(name).apply(params);
}

To my mind, the better way to generate "dynamic" predicates is with static methods:

public class PredicatesQuestion {

    public static void main(String[] args) {
        List<Person> people = Arrays.asList(new Person(20, Gender.MALE),
                new Person(45, Gender.FEMALE), new Person(50, Gender.MALE),
                new Person(65, Gender.MALE));

        people.stream()
                .filter(personIsBetweenAges(16, 25))
                .forEach(person -> {
                    System.out.println(person.getAge() + ", " + person.getGender());
                });
    }

    private static Predicate<Person> personIsMale() {
        return person -> person.getGender() == Gender.MALE;
    }

    private static Predicate<Person> personIsBetweenAges(int lower, int upper) {
        return personIsAtLeast(lower).and(personIsYoungerThan(upper));
    }

    private static Predicate<Person> personIsAtLeast(int age) {
        return person -> person.getAge() >= age;
    }

    private static Predicate<Person> personIsYoungerThan(int age) {
        return person -> person.getAge() < age;
    }
}

It's then trivial to create descriptive predicates as required:

private static Predicate<Person> personIsOfDrivingAge() {
    return personIsAtLeast(17);
}

private static Predicate<Person> couldBePilot() {
    return personIsBetweenAges(23, 65).and(personIsMale());

}

And the sort of thing you're trying to achieve with overriding some parameters remains clear with a bit of composition:

people.stream()
        .filter(couldBePilot().and(personIsBetweenAges(45, 56)))
        .forEach(person -> {
            System.out.println(person.getAge() + ", " + person.getGender());
        });
Smithy answered 23/8, 2015 at 13:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.