Chapter 16. Recommendations

Movies! Friends! Bargains!

In the last part of this exercise we wanted to add recommendations to the app. One obvious recommendation was movies that our friends liked (and their friends too, but with less importance). The second recommendation was for new friends that also liked the movies that we liked most.

Doing these kinds of ranking algorithms is a lot of fun with graph databases. The algorithms are implemented by traversing the graph in a certain order, collecting information on the go, and deciding which paths to follow and what to include in the results.

We were only interested in recommendations of a certain degree of friends.

Example 16.1. Recommendations

public Map<Movie,Integer> recommendMovies(User user, final int ratingDistance) {
    final DynamicRelationshipType RATED = withName(User.RATED);
    final Map<Long,int[]> ratings=new HashMap<Long, int[]>();
    TraversalDescription traversal= Traversal.description().breadthFirst()
        .relationships(withName(User.FRIEND)).relationships(RATED, OUTGOING)
        .evaluator(new Evaluator() {

      public Evaluation evaluate(Path path) {
          final int length = path.length() - 1;
          // only as far as requested
          if (length > ratingDistance) return Evaluation.EXCLUDE_AND_PRUNE;
          Relationship rating = path.lastRelationship();
          // process RATED relationships, not FRIEND
          if (rating != null && rating.getType().equals(RATED)) {
              // my rated movies
              if (length == 0) return Evaluation.EXCLUDE_AND_PRUNE;
              final long movieId = rating.getEndNode().getId();
              int[] stars = ratings.get(movieId);
              if (stars == null) {
                  stars = new int[2];
                  ratings.put(movieId, stars);
              }
              // aggregate for averaging, inverse to distance
              int weight = ratingDistance - length;
              stars[0] += weight * (Integer) rating.getProperty("stars", 0);
              stars[1] += weight;
              return Evaluation.INCLUDE_AND_PRUNE;
          }
          return Evaluation.EXCLUDE_AND_CONTINUE;
      }
    });

    Map<Movie,Integer> result=new HashMap<Movie, Integer>();
    // lazy traversal results
    final Iterable<Movie> movies = movieRepository.findAllByTraversal(user, traversal);
    for (Movie movie : movies) { // assign movie to averaged rating
      final int[] stars = ratings.get(movie.getNodeId());
      result.put(movie, stars[0]/stars[1]);
    }
    return result;
}


The UserController simply called this method, added its results to the model, and the view rendered the recommendation alongside the user's own ratings.