Выразительная сила Cypher уже удивительна и улучшается с выпуском Neo4j 2.0 . Давайте сделаем шаг назад от передового края и увидим Cypher в 1.9.4 и как это можно перевести на Java. Сначала простой пример, где мы ищем узел User по индексу и возвращаем список имен пользователей, принадлежащих людям, которые являются друзьями этого пользователя:
START me = node:Users(username='maxdemarzi') MATCH me -[:FRIENDS]-> people RETURN people.username
Заявление Cypher выражает то, что я хочу, даже лучше, чем мое испорченное объяснение на английском языке. Итак, как бы мы сделали это в Neo4j Java API ?
ObjectMapper objectMapper = new ObjectMapper(); private static final RelationshipType FRIENDS = DynamicRelationshipType.withName("FRIENDS"); @GET @Path("/friends/{username}") public Response getFriends(@PathParam("username") String username, @Context GraphDatabaseService db) throws IOException { List<String> results = new ArrayList<String>(); IndexHits<Node> users = db.index().forNodes("Users").get("username", username); Node user = users.getSingle(); for ( Relationship relationship : user.getRelationships(FRIENDS, Direction.OUTGOING) ){ Node friend = relationship.getEndNode(); results.add((String)friend.getProperty("username")); } return Response.ok().entity(objectMapper.writeValueAsString(results)).build(); }
Это немного многословно, но не ужасно. Мы ищем имя пользователя в индексе, а затем пересекаем отношения Outgoing FRIENDS с дружественными узлами и получаем их свойства имени пользователя. Как насчет того, чтобы попробовать что-то немного сложнее:
START me = node:Users(username='maxdemarzi') MATCH me -[:FRIENDS]- people -[:FRIENDS]- fof WHERE NOT(me -[:FRIENDS]- fof) RETURN fof, COUNT(people) AS friend_count ORDER BY friend_count DESC LIMIT 10
В приведенном выше запросе мы получаем друзей друзей, которые не являются прямыми друзьями, и возвращаем топ-10, упорядоченные по количеству общих друзей, в порядке убывания. Мы собираемся извлечь части этого запроса в их собственные методы, чтобы сделать нашу жизнь проще, поэтому сейчас давайте просто обманем и притворимся, что это будет работать:
@GET @Path("/fofs/{username}") public Response getFofs(@PathParam("username") String username, @Context GraphDatabaseService db) throws IOException { List<Map<String, Object>> results = new ArrayList<Map<String, Object>>(); HashMap<Node, AtomicInteger> fofs = new HashMap<Node, AtomicInteger>(); IndexHits<Node> users = db.index().forNodes("Users").get("username", username); Node user = users.getSingle(); findFofs(fofs, user); List<Entry<Node, AtomicInteger>> fofList = orderFofs(fofs); returnFofs(results, fofList.subList(0, Math.min(fofList.size(), 10))); return Response.ok().entity(objectMapper.writeValueAsString(results)).build(); }
Мы вызываем метод «findFofs», чтобы получить друзей друзей и их количество. Сначала мы найдем друзей пользователя и поместим их в список. Во-вторых, мы найдем друзей этих друзей, и пока они не являются теми пользователями, с которыми мы начинали, или они уже являются друзьями этого пользователя, мы добавляем их в HashMap, поскольку они вводят ключи и увеличивают их каждый раз, когда мы видим их снова.
private void findFofs(HashMap<Node, AtomicInteger> fofs, Node user) { List<Node> friends = new ArrayList<Node>(); if (user != null){ for ( Relationship relationship : user.getRelationships(FRIENDS, Direction.BOTH) ){ Node friend = relationship.getOtherNode(user); friends.add(friend); } for ( Node friend : friends ){ for (Relationship otherRelationship : friend.getRelationships(FRIENDS, Direction.BOTH) ){ Node fof = otherRelationship.getOtherNode(friend); if (!user.equals(fof) && !friends.contains(fof)) { AtomicInteger atomicInteger = fofs.get(fof); if (atomicInteger == null) { fofs.put(fof, new AtomicInteger(1)); } else { atomicInteger.incrementAndGet(); } } } } } }
Далее нам нужно заказать наш список друзей друзей в порядке убывания следующим способом:
private List<Entry<Node, AtomicInteger>> orderFofs(HashMap<Node, AtomicInteger> fofs) { List<Entry<Node, AtomicInteger>> fofList = new ArrayList<Entry<Node, AtomicInteger>>(fofs.entrySet()); Collections.sort(fofList, new Comparator<Entry<Node, AtomicInteger>>() { @Override public int compare(Entry<Node, AtomicInteger> a, Entry<Node, AtomicInteger> b) { return ( b.getValue().get() - a.getValue().get() ); } }); return fofList; }
Наконец, мы получим все свойства узлов друзей и друзей и добавим их в наши результаты.
private void returnFofs(List<Map<String, Object>> results, List<Entry<Node, AtomicInteger>> fofList) { Map<String, Object> resultsEntry; Map<String, Object> fofEntry; Node fof; for (Entry<Node, AtomicInteger> entry : fofList) { resultsEntry = new HashMap<String, Object>(); fofEntry = new HashMap<String, Object>(); fof = entry.getKey(); for (String prop : fof.getPropertyKeys()) { fofEntry.put(prop, fof.getProperty(prop)); } resultsEntry.put("fof", fofEntry); resultsEntry.put("friend_count", entry.getValue()); results.add(resultsEntry); } }
Обратите внимание, что когда мы вызывали этот метод, мы передавали только 10 лучших друзей друзей, уже заказанных этим другом.
returnFofs(results, fofList.subList(0, Math.min(fofList.size(), 10)));
Наши 6 строк Cypher быстро взлетели до 70 строк Java. Смотрите полное неуправляемое расширение с тестами на github.
Я думаю, что такие посты забавны, и они действительно показывают силу Cypher с точки зрения выразительности, читабельности и ловкости. Я сделаю еще несколько в ближайшее время. Напишите мне, если у вас есть какие-то конкретные запросы шифров, которые вы хотели бы видеть переведенными.