/*
TagRecommender:
A framework to implement and evaluate algorithms for the recommendation
of tags.
Copyright (C) 2013 Dominik Kowald, Emanuel Lacic
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package itemrecommendations;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import processing.BLLCalculator;
import common.Bookmark;
import common.Utilities;
import file.BookmarkReader;
/**
* Tag-weighted, time-weighted and combined strategy used by Zheng & Li
* @author elacic
*
*/
public class ZhengApproach {
Map<Integer, Map<Integer, List<Integer>>> userResourceTagMaping;
Map<Integer, Map<Integer, Long>> userResourceTimeMaping;
Map<Integer, Long> userMaxTimeMaping;
Map<Integer, Long> userMinTimeMaping;
public ZhengApproach(List<Bookmark> trainList) {
userResourceTagMaping = new HashMap<Integer, Map<Integer,List<Integer>>>();
userResourceTimeMaping = new HashMap<Integer, Map<Integer, Long>>();
userMaxTimeMaping = new HashMap<Integer, Long>();
userMinTimeMaping = new HashMap<Integer, Long>();
for (Bookmark trainData : trainList) {
Integer userID = trainData.getUserID();
Integer resID = trainData.getResourceID();
List<Integer> tags = trainData.getTags();
Long day = TimeUnit.SECONDS.toDays(Long.parseLong(trainData.getTimestamp()));
Map<Integer, List<Integer>> resourceTagMaping = userResourceTagMaping.get(userID);
Map<Integer, Long> resourceTimeMaping = userResourceTimeMaping.get(userID);
if (resourceTagMaping == null) {
resourceTagMaping = new HashMap<Integer, List<Integer>>();
}
if (resourceTimeMaping == null) {
resourceTimeMaping = new HashMap<Integer, Long>();
}
resourceTagMaping.put(resID, tags);
resourceTimeMaping.put(resID, day);
Long maxUserTime = userMaxTimeMaping.get(userID);
Long minUserTime = userMinTimeMaping.get(userID);
if (maxUserTime == null) {
maxUserTime = day;
minUserTime = day;
} else {
if (maxUserTime < day) {
maxUserTime = day;
}
if (minUserTime > day) {
minUserTime = day;
}
}
userMaxTimeMaping.put(userID, maxUserTime);
userMinTimeMaping.put(userID, minUserTime);
userResourceTagMaping.put(userID, resourceTagMaping);
userResourceTimeMaping.put(userID, resourceTimeMaping);
}
}
/**
* Gets the tag weight for a resource of a user
* @param userID user
* @param resourceID respource with tags from user
* @return tag weight
*/
public Double getTagWeight(Integer userID, Integer resourceID) {
if (! userResourceTagMaping.containsKey(userID) ||
! userResourceTagMaping.get(userID).containsKey(resourceID)) {
return 0.0;
}
//get user's tag for the provided resource
Map<Integer, List<Integer>> resourceTagMaping = userResourceTagMaping.get(userID);
List<Integer> tags = resourceTagMaping.get(resourceID);
Double resourceTagScore = 0.0;
List<Integer> tagsByUser = new ArrayList<Integer>();
Set<Integer> uniqueTagsUsedByUser = new HashSet<Integer>();
for (List<Integer> tagsAtResource : resourceTagMaping.values()) {
tagsByUser.addAll(tagsAtResource);
uniqueTagsUsedByUser.addAll(tagsAtResource);
}
for (Integer tag : tags) {
resourceTagScore += getTagScore(userID, tag, tagsByUser, uniqueTagsUsedByUser);
}
return resourceTagScore;
}
/**
* Calculates the tag score for the tag-weighted strategy
* @param userID the user using the provided tag
* @param tag the tag for which the tag score will be calculated
* @param uniqueTagsUsedByUser unique tags used by the user
* @param tagsByUser all tags used by the user (a tag can occur multiple times if it has been used at more resources)
* @return tag score
*/
private Double getTagScore(int userID, int tag,
List<Integer> tagsByUser, Set<Integer> uniqueTagsUsedByUser) {
Double tagScore = (double) Collections.frequency(tagsByUser, tag);
Integer frequencySum = 0;
for (Integer uniqueTagUsed : uniqueTagsUsedByUser) {
frequencySum += Collections.frequency(tagsByUser, uniqueTagUsed);
}
return tagScore / (double) frequencySum;
}
/**
* Calculates the time weight which denotes the degree with which a user's interests have declined
* @param userID the user
* @param resourceID
* @return
*/
public Double getTimeWeight(int userID, int resourceID) {
Long minTime = userMinTimeMaping.get(userID);
Long maxTime = userMaxTimeMaping.get(userID);
if (minTime == null && maxTime == null) {
minTime = 0L;
maxTime = 1L;
}
if (minTime == maxTime) {
minTime = 0L;
}
double hlu = maxTime - minTime; // TODO: half life of each user
return Math.exp( - Math.log(2) * getTime(userID, resourceID) / hlu );
}
/**
* Takes the value of 0 for the last tagging day of a user, 1 for the penultimate tagging day, etc.
* In other words, the maximum day of the user (the last day) minus the day of tagging the resource
* @param userId
* @param resourceID
* @return
*/
private Long getTime(int userId, int resourceID) {
Long time = 0L;
if (userResourceTimeMaping.containsKey(userId) && userResourceTimeMaping.get(userId).containsKey(resourceID)) {
time = userMaxTimeMaping.get(userId) - userResourceTimeMaping.get(userId).get(resourceID);
}
return time;
}
/**
* Calculates the combined tag-time weight
* @param userID user
* @param resourceID resource of the user
* @param lambda parameter to adjust the significance of the tag and time weights
* @return tag-time weight
*/
public Double getTagTimeWeight(int userID, int resourceID, double lambda) {
return (lambda * getTagWeight(userID, resourceID)) + ((1 - lambda) * getTimeWeight(userID, resourceID));
}
/**
* Calculates the combined tag-time weight
* @param tagWeight
* @param timeWeight
* @param lambda
* @return
*/
public Double getTagTimeWeight(Double tagWeight, Double timeWeight, double lambda) {
return (lambda * tagWeight) + ((1 - lambda) * timeWeight);
}
}