package com.springone.myrestaurants.domain;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.graphdb.traversal.Traverser;
import org.neo4j.helpers.Predicate;
import org.neo4j.kernel.Traversal;
import org.neo4j.kernel.impl.traversal.TraversalDescriptionImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import java.util.*;
/**
* @author Michael Hunger
* @since 02.10.2010
*/
@Configurable
public class TopRatedRestaurantFinder {
@Autowired
GraphDatabaseContext graphDatabaseContext;
private static final int MAXIMUM_DEPTH = 5;
public Collection<RatedRestaurant> getTopNRatedRestaurants(final UserAccount user, final int n) {
final CalculateRatingPredicate calculateRatingPredicate = new CalculateRatingPredicate();
final Node userNode=user.getPersistentState();
final TraversalDescription traversalDescription = new TraversalDescriptionImpl()
.order(Traversal.postorderBreadthFirst())
.prune(Traversal.pruneAfterDepth(MAXIMUM_DEPTH))
.filter(calculateRatingPredicate)
.relationships(DynamicRelationshipType.withName("friends"));
final Traverser traverser = traversalDescription.traverse(userNode);
final Iterator<Node> it = traverser.nodes().iterator();
while (it.hasNext()) {
it.next();
}
return calculateRatingPredicate.getRecommendedRestaurants(n);
}
private class CalculateRatingPredicate implements Predicate<Path> {
class AggregatedRecommendation implements Comparable<AggregatedRecommendation> {
private Node restaurant;
private Collection<Relationship> recommendations=new HashSet<Relationship>();
double stars;
double sum;
public AggregatedRecommendation(final Node restaurant) {
this.restaurant = restaurant;
}
@Override
public int compareTo(final AggregatedRecommendation o) {
return stars < o.stars ? 1 : stars == o.stars ? 0 : -1;
}
public void add(final Relationship recommendation, final int distance) {
if (recommendations.add(recommendation)) {
final Integer userStars = (Integer)recommendation.getProperty("stars", 0);
sum += userStars * Math.pow(0.5D,distance);
stars = sum / recommendations.size();
}
}
private RatedRestaurant toRatedRestaurant(final CalculateRatingPredicate calculateRatingPredicate) {
final RatedRestaurant ratedRestaurant = new RatedRestaurant(graphDatabaseContext.createEntityFromState(restaurant, Restaurant.class));
for (final Relationship recommendation : recommendations) {
ratedRestaurant.add(graphDatabaseContext.createEntityFromState(recommendation, Recommendation.class));
}
return ratedRestaurant;
}
}
private final Map<Node, AggregatedRecommendation> recommendedRestaurants=new HashMap<Node, AggregatedRecommendation>();
public CalculateRatingPredicate() {
}
public boolean accept(final Path path) {
final int distance = path.length();
final Node friend = path.endNode();
for (final Relationship recommendation : friend.getRelationships(DynamicRelationshipType.withName("recommends"))) {
final Node restaurant = recommendation.getEndNode();
if (!recommendedRestaurants.containsKey(restaurant)) {
recommendedRestaurants.put(restaurant,new AggregatedRecommendation(restaurant));
}
recommendedRestaurants.get(restaurant).add(recommendation,distance);
}
return true;
}
public Collection<RatedRestaurant> getRecommendedRestaurants(final int n) {
final List<AggregatedRecommendation> sorted = new ArrayList<AggregatedRecommendation>(recommendedRestaurants.values());
Collections.sort(sorted);
int index=0;
final Collection<RatedRestaurant> result=new ArrayList<RatedRestaurant>(n);
for (final AggregatedRecommendation aggregatedRecommendation : sorted) {
if (index == n) return result;
result.add(aggregatedRecommendation.toRatedRestaurant(this));
index++;
}
return result;
}
}
}