I'm working on a procedure that should find the lowest-weighted path between two nodes using Dijkstra's algorithm. The procedure should only return paths whose all nodes match specific criteria (i.e. all nodes should have properties with specific values). If at least one node in a path doesn't match the criteria, then the path becomes invalid, and the algorithm should look for the next lowest-weighted path.
In order to achieve this, I'm using a PathExpanderBuilder
with node filters, but they don't seem to filter anything.
Here is my code:
public class Demo {
@Procedure
@Description("apoc.algo.dijkstraWithFilters(startNode, endNode, " +
"'distance', 10, 'prop1', 2, 'prop2', [100, 200], 'prop3') " +
" YIELD path, weight - run dijkstra with relationship property name as cost function" +
" and a default weight if the property does not exist")
public Stream<WeightedPathResult> dijkstraWithFilters(
@Name("startNode") Node startNode,
@Name("endNode") Node endNode,
@Name("weightPropertyName") String weightPropertyName,
@Name("defaultWeight") double defaultWeight,
@Name("longPropName") String longPropName,
@Name("longPropValue") long longPropValue,
@Name("listPropName") String listPropName,
@Name("listPropValues") List<Long> listPropValues,
@Name("boolPropName") String boolPropName) {
PathFinder<WeightedPath> algo = GraphAlgoFactory.dijkstra(
buildPathExpanderByPermissions(longPropName, longPropValue, listPropName, listPropValues, boolPropName),
(relationship, direction) -> convertToDouble(relationship.getProperty(weightPropertyName, defaultWeight))
);
return WeightedPathResult.streamWeightedPathResult(startNode, endNode, algo);
}
private double convertToDouble(Object property) {
if (property instanceof Double)
return (double) property;
else if (property instanceof Long)
return ((Long) property).doubleValue();
else if (property instanceof Integer)
return ((Integer) property).doubleValue();
return 1;
}
private PathExpander<Object> buildPathExpanderByPermissions(
String longPropName,
long longPropValue,
String listPropName,
List<Long> listPropValue,
String boolPropName
) {
PathExpanderBuilder builder = PathExpanderBuilder.allTypesAndDirections();
builder.addNodeFilter(
node -> !node.hasProperty(longPropName) ||
node.getProperty(longPropName) instanceof Long &&
(long) node.getProperty(longPropName) < longPropValue
);
builder.addNodeFilter(
node -> {
try {
return !node.hasProperty(listPropName) ||
(boolean) node.getProperty(boolPropName, false) ||
!Collections.disjoint((List<Long>) node.getProperty(listPropName), listPropValue);
}
catch (Exception e){
return false;
}
}
);
return builder.build();
}
}
What am I missing here? Am I making a wrong use of PathExpanderBuilder
?
PathExpanderBuilder's are immutable and so calling e.g. addNodeFilter returns a new PathExpanderBuilder with the added filter and so you need to re-assign your
builder
with that returned instance.