How would I combine BiPredicate and Predicate?

7.2k Views Asked by At

I have two lambda functions (predicates):

final Predicate<Node> isElement = node -> node.getNodeType() == Node.ELEMENT_NODE;
final BiPredicate<Node, String> hasName = (node, name) -> node.getNodeName().equals(name);

Which I want to combine in some concise way, something like this:

// Pseudocode
isElement.and(hasName("tag")) // type of Predicate

and then pass to another lambda function:

final BiFunction<Node, Predicate<Node>, List<Node>> getChilds = (node, cond) -> {
    List<Node> resultList = new ArrayList<>();
    NodeList nodeList = node.getChildNodes();
    for (int i = 0; i < nodeList.getLength(); ++i) {
        Node tmp = nodeList.item(i);
        if (cond.test(tmp)) {
            resultList.add(tmp);
        }
    }
    return resultList;
};

As a result I'm expecting it would look like the following:

List<Node> listNode = getChilds.apply(document, isElement.and(hasName("tag")));

But and method of Predicate doesn't accept BiPredicate parameter.

How I could do this?

3

There are 3 best solutions below

8
On

You need to write a static helper function that curries your BiPredicate down to a Predicate which you can then .and() with the other predicate.

isElement.and(curryRight(hasName, "tag"))

alternatively, you can just do this:

isElement.and(node -> hasName.test(node, "tag"))

it's not that much longer

0
On

Change the lambada function hasName to a function hasName(String name) returns lambda function.

final Predicate<Node> hasName(String name) { return node -> node.getNodeName().equals(name); }

Then your code below will work.

List<Node> listNode = getChilds.apply(document, isElement.and(hasName("tag")));
0
On

Stop rewriting every method into a lambda expression. There is no real benefit. If you have an ordinary method, you can call it by its simple name without having to append apply, test or similar. If you really need a function then, you can still create a static method reference using the :: operator.

So if you want to improve you code, think about using the new API instead of overusing the Java language features. For example:

static List<Node> getChilds(Node node, Predicate<Node> cond) {
    NodeList nodeList = node.getChildNodes();
    return IntStream.range(0, nodeList.getLength()).mapToObj(nodeList::item)
                    .filter(cond).collect(Collectors.toList());
}

Regarding you attempt to combine Predicates. Of course, you can express everything as a function without compromise. For example:

Predicate<Node> isElement = node -> node.getNodeType() == Node.ELEMENT_NODE;
Function<Node, String> nodeName = Node::getNodeName;
Predicate<Node> both = isElement.and(nodeName.andThen("tag"::equals)::apply);

but is this really an improvement?

You may simply write

Predicate<Node> both = isElement.and(n -> n.getNodeName().equals("tag"));

or, even simpler, since Nodes not representing ELEMENT nodes will never report a node name of "tag" you don't need the first predicate at all and the entire operation becomes:

getChilds(document, n -> "tag".equals(n.getNodeName()));

That might not feel as fancy as complicated function composition, but it's a practical solution.