В Расширении Neo4j я показал вам, как создать неуправляемое расширение для разогрева кеша узлов и отношений. Давайте попробуем сделать что-нибудь более интересное, например, разоблачить алгоритм поиска A * (A Star) через REST API. График, который мы создали ранее, выглядит так:
Я создал сетку узлов 5 на 5 со свойствами x и y и связями между ними «next_to» со случайным временным свойством. Свойства x и y узла говорят нам, где вдоль сетки находится этот узел, а свойство time между двумя смежными узлами говорит нам, сколько времени требуется для перемещения по соединяющему их пути. A * находит путь с наименьшей стоимостью между начальным узлом и конечной точкой.
A * должен знать: начальный и конечный узлы, какой-то способ оценить расстояние до конечного узла и стоимость обхода отношений. С этой информацией A * перемещается по графику по пути с наименьшей известной эвристической стоимостью, которая является просто суммой стоимости пройденного в данный момент пути и оценки для конечного узла.
Посмотрите видео ниже для наглядной демонстрации алгоритма A * в действии.
Чтобы сделать это в Neo4j, мы добавили бы метод в файл MyService.java, который мы создали ранее.
import org.neo4j.graphalgo.*;
import org.neo4j.kernel.Traversal;
@GET
@Path("/astar/{from}/{to}")
@Produces("application/json")
public Response routeAStar(@PathParam("from") Long from, @PathParam("to") Long to,
@Context GraphDatabaseService db) throws IOException {
Node nodeA = db.getNodeById(from);
Node nodeB = db.getNodeById(to);
EstimateEvaluator<Double> estimateEvaluator = new EstimateEvaluator<Double>()
{
public Double getCost( final Node node, final Node goal )
{
double dx = (Double) node.getProperty( "x" ) - (Double) goal.getProperty( "x" );
double dy = (Double) node.getProperty( "y" ) - (Double) goal.getProperty( "y" );
double result = Math.sqrt( Math.pow( dx, 2 ) + Math.pow( dy, 2 ) );
return result;
}
};
PathFinder<WeightedPath> astar = GraphAlgoFactory.aStar(
Traversal.pathExpanderForAllTypes(),
CommonEvaluators.doubleCostEvaluator( "time" ), estimateEvaluator );
WeightedPath path = astar.findSinglePath( nodeA, nodeB );
return Response.ok().entity( path.toString() ).build();
}
Мы создали EstimateEvaluator , что в данном случае используется евклидово расстояние для его эвристики и свойства времени для фактической стоимости прохождения отношений. Мы используем GraphAlgoFactory и метод aStar, перебирая все типы отношений с помощью pathExpander .
Давайте попробуем перейти от узла 1 к узлу 12 в панели администратора REST:
Мы получаем путь от узла 1 до 2, от 7 до 12 с весом 8,0 (если вы попробуете это дома, ваши числа могут отличаться, поскольку ваш график будет генерироваться случайным образом).
get /example/service/astar/1/12 (1)--[next_to,1]-->(2)--[next_to,2]-->(7)--[next_to,12]-->(12) weight:8.0
Мне нравится компактное представление пути, но что, если я хочу увидеть больше, чем это? Например, я хочу вернуть хорошее JSON-представление времени, которое занимает путь, узлов и отношений в пути, и их соответствующих свойств? Я могу создать HashMap и добавить к нему нужные мне кусочки:
Map<String, Object> astarMap = new HashMap<String, Object>();
astarMap.put("time", path.weight());
List<Object> nodes = new ArrayList<Object>();
for ( Node node : path.nodes() )
{
Map<String, Object> nodeMap = new HashMap<String, Object>();
nodeMap.put("id", node.getId());
nodeMap.put("x", node.getProperty("x"));
nodeMap.put("y", node.getProperty("y"));
nodes.add(nodeMap);
}
astarMap.put("nodes", nodes);
List<Object> relationships = new ArrayList<Object>();
for ( Relationship relationship : path.relationships() )
{
Map<String, Object> relMap = new HashMap<String, Object>();
relMap.put("id", relationship.getId());
relMap.put("rel_type", relationship.getType().name());
relMap.put("start_node", relationship.getStartNode().getId());
relMap.put("end_node", relationship.getEndNode().getId());
relMap.put("time", relationship.getProperty("time"));
relationships.add(relMap);
}
astarMap.put("relationships", relationships);
ObjectMapper objectMapper = new ObjectMapper();
return Response.ok().entity(objectMapper.writeValueAsString(astarMap)).build();
Когда я снова запускаю команду get, я теперь возвращаю:
{"time":8.0,
"nodes":[{"id":1,"y":0.0,"x":0.0},
{"id":2,"y":1.0,"x":0.0},
{"id":7,"y":1.0,"x":1.0},
{"id":12,"y":1.0,"x":2.0}],
"relationships":[{"start_node":1,"id":1,"time":3.0,"rel_type":"next_to","end_node":2},
{"start_node":2,"id":2,"time":4.0,"rel_type":"next_to","end_node":7},
{"start_node":7,"id":12,"time":1.0,"rel_type":"next_to","end_node":12}]
}
Что немного более многословно, но легче иметь дело. Если вы хотите узнать больше об A * и поиске путей, я рекомендую блог Амит Патель о программировании игр . Код как всегда доступен на github .
С неуправляемыми расширениями вы можете получить свой торт и съесть его тоже. Полная мощность и скорость встроенного Java API по сравнению с REST. Используйте это с умом и наслаждайтесь.


