/// <summary> /// Spreading activation recommender. /// If the user is logged in, we remove books already rated by him from the recommendation. /// </summary> /// <param name="bookId">Id of book on which will the recommendation be based</param> /// <param name="userId">Id of the signed in user</param> /// <param name="howMany">Depth of activation spreading</param> /// <returns>List of books with the highest activation value</returns> public List <int> Recommend(int bookId, string userId, int howMany = 6) { // initialize the activation graph GraphOfBooks graph = new GraphOfBooks(); graph.AddVertex(bookId, START_ACTIVATION_VALUE); List <int> bookIDs = new List <int>(); bookIDs.Add(bookId); // start recursive spreading activation RunOneLevelBFS(maxLevel, bookIDs, graph); graph.RemoveVertex(bookId); List <Tuple <int, double> > bookIDVWithActivationValues = graph.ExportBookIDVerticesWithActivationValues(); BookRecommenderContext bd = new BookRecommenderContext(); List <Tuple <int, double> > bookIdsWithActivationValuesWithoutRated = removeAlreadyRatedBooks(bookIDVWithActivationValues, userId, bd); // sorting books according to their importance from the highest to the lowest activation value List <Tuple <int, double> > sortedBookIdsWithActivationValues = bookIdsWithActivationValuesWithoutRated.OrderBy(v => v.Item2).Reverse().ToList(); // return sorted sublist of recommendation return(sortedBookIdsWithActivationValues.Select(v => v.Item1).Take(howMany).ToList()); }
private void RunOneLevelBFS(int levelI, List <int> bookIDsPreviousLevel, GraphOfBooks graph) { // end of recursion if (levelI == 0) { return; } SpreadingRecommenderCache model = simCacheModel.spreadingRecommenderCache; // get next level of similar verteces List <int> bookIDsForNextLevel = new List <int>(); foreach (int bookIdFromI in bookIDsPreviousLevel) { List <Tuple <int, int> > simBooksWithQuantitiesUnsortedI = model.GetSimilaritiesBooksWithQuantitiesByAll(bookIdFromI); List <Tuple <int, int> > simBooksWithQuantitiesSortedI = simBooksWithQuantitiesUnsortedI.OrderBy(v => v.Item2).Reverse().ToList(); List <Tuple <int, int> > simBooksWithQuantitiesI = simBooksWithQuantitiesSortedI.Take(numberOfSimilarNeighbors).ToList(); // add neighbors for next level bookIDsForNextLevel.AddRange( simBooksWithQuantitiesI.Select(b => b.Item1)); // vertex was inserted to graph in the last iteration double?activationValueObjI = graph.GetActivationValueOfVertex(bookIdFromI); int numberOfneighborsI = simBooksWithQuantitiesI.Select(b => b.Item2).Sum(); if (numberOfneighborsI == 0) { continue; } // update current vertex graph.SetVertex(bookIdFromI, activationValueObjI.Value * MULTIPLICATOR_OF_ACTIVATION_PRESERVING); // computes difference of activation value for distribution to neighbors double activationValueForDistributionI = activationValueObjI.Value * (1 - MULTIPLICATOR_OF_ACTIVATION_PRESERVING) / numberOfneighborsI; // re-compute weights of neighbors foreach (Tuple <int, int> neighborBookIdsWithQuantityI in simBooksWithQuantitiesI) { int neighborBookIdI = neighborBookIdsWithQuantityI.Item1; double neighborQuantityI = neighborBookIdsWithQuantityI.Item2; double activationValueDiff = neighborQuantityI * activationValueForDistributionI; graph.IncreaseActivationValueOfVertex(neighborBookIdI, activationValueDiff); } } List <Tuple <int, int> > bookIDsForNextLevelAndTheirQuantities = bookIDsForNextLevel.GroupBy(b => b) .Select(group => new Tuple <int, int>(group.Key, group.Count())).ToList(); List <Tuple <int, int> > sortedBookIDsForNextLevelAndTheirQuantities = bookIDsForNextLevelAndTheirQuantities.OrderBy(v => v.Item2).Reverse().ToList(); List <int> bookIDsForNextLevelNoDuplicate = sortedBookIDsForNextLevelAndTheirQuantities .Select(b => b.Item1).Take(numberOfNeighborsForNextLevel).ToList(); if (DEBUG) { System.Console.WriteLine($"Level {levelI} finished, next leven itemCount: {bookIDsPreviousLevel.Count}"); graph.printTopKOfGraph(10); } // recursion for next level RunOneLevelBFS(levelI - 1, bookIDsForNextLevelNoDuplicate, graph); }